/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * Portions Copyright (C) Philipp Kewisch */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ICAL = factory());
})(this, (function () { 'use strict';

  function _assertClassBrand(e, t, n) {
    if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
    throw new TypeError("Private element is not present on this object");
  }
  function _callSuper(t, o, e) {
    return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));
  }
  function _construct(t, e, r) {
    if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments);
    var o = [null];
    o.push.apply(o, e);
    var p = new (t.bind.apply(t, o))();
    return r && _setPrototypeOf(p, r.prototype), p;
  }
  function _isNativeReflectConstruct() {
    try {
      var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
    } catch (t) {}
    return (_isNativeReflectConstruct = function () {
      return !!t;
    })();
  }
  function _iterableToArrayLimit(r, l) {
    var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
    if (null != t) {
      var e,
        n,
        i,
        u,
        a = [],
        f = !0,
        o = !1;
      try {
        if (i = (t = t.call(r)).next, 0 === l) {
          if (Object(t) !== t) return;
          f = !1;
        } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
      } catch (r) {
        o = !0, n = r;
      } finally {
        try {
          if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
        } finally {
          if (o) throw n;
        }
      }
      return a;
    }
  }
  function _toPrimitive(t, r) {
    if ("object" != typeof t || !t) return t;
    var e = t[Symbol.toPrimitive];
    if (void 0 !== e) {
      var i = e.call(t, r || "default");
      if ("object" != typeof i) return i;
      throw new TypeError("@@toPrimitive must return a primitive value.");
    }
    return ("string" === r ? String : Number)(t);
  }
  function _toPropertyKey(t) {
    var i = _toPrimitive(t, "string");
    return "symbol" == typeof i ? i : i + "";
  }
  function _typeof(o) {
    "@babel/helpers - typeof";

    return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
      return typeof o;
    } : function (o) {
      return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
    }, _typeof(o);
  }
  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }
  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
    }
  }
  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    Object.defineProperty(Constructor, "prototype", {
      writable: false
    });
    return Constructor;
  }
  function _defineProperty(obj, key, value) {
    key = _toPropertyKey(key);
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }
    return obj;
  }
  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    Object.defineProperty(subClass, "prototype", {
      writable: false
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }
  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }
  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
    return _setPrototypeOf(o, p);
  }
  function _isNativeFunction(fn) {
    try {
      return Function.toString.call(fn).indexOf("[native code]") !== -1;
    } catch (e) {
      return typeof fn === "function";
    }
  }
  function _wrapNativeSuper(Class) {
    var _cache = typeof Map === "function" ? new Map() : undefined;
    _wrapNativeSuper = function _wrapNativeSuper(Class) {
      if (Class === null || !_isNativeFunction(Class)) return Class;
      if (typeof Class !== "function") {
        throw new TypeError("Super expression must either be null or a function");
      }
      if (typeof _cache !== "undefined") {
        if (_cache.has(Class)) return _cache.get(Class);
        _cache.set(Class, Wrapper);
      }
      function Wrapper() {
        return _construct(Class, arguments, _getPrototypeOf(this).constructor);
      }
      Wrapper.prototype = Object.create(Class.prototype, {
        constructor: {
          value: Wrapper,
          enumerable: false,
          writable: true,
          configurable: true
        }
      });
      return _setPrototypeOf(Wrapper, Class);
    };
    return _wrapNativeSuper(Class);
  }
  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
  }
  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    } else if (call !== void 0) {
      throw new TypeError("Derived constructors may only return object or undefined");
    }
    return _assertThisInitialized(self);
  }
  function _slicedToArray(arr, i) {
    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
  }
  function _arrayWithHoles(arr) {
    if (Array.isArray(arr)) return arr;
  }
  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }
  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
    return arr2;
  }
  function _nonIterableRest() {
    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }
  function _createForOfIteratorHelper(o, allowArrayLike) {
    var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
    if (!it) {
      if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
        if (it) o = it;
        var i = 0;
        var F = function () {};
        return {
          s: F,
          n: function () {
            if (i >= o.length) return {
              done: true
            };
            return {
              done: false,
              value: o[i++]
            };
          },
          e: function (e) {
            throw e;
          },
          f: F
        };
      }
      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }
    var normalCompletion = true,
      didErr = false,
      err;
    return {
      s: function () {
        it = it.call(o);
      },
      n: function () {
        var step = it.next();
        normalCompletion = step.done;
        return step;
      },
      e: function (e) {
        didErr = true;
        err = e;
      },
      f: function () {
        try {
          if (!normalCompletion && it.return != null) it.return();
        } finally {
          if (didErr) throw err;
        }
      }
    };
  }

  /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   * Portions Copyright (C) Philipp Kewisch */
  /**
   * Represents the BINARY value type, which contains extra methods for encoding and decoding.
   *
   * @class
   * @alias ICAL.Binary
   */
  var Binary = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Binary instance
     *
     * @param {String} aValue     The binary data for this value
     */
    function Binary(aValue) {
      _classCallCheck(this, Binary);
      /**
       * The type name, to be used in the jCal object.
       * @default "binary"
       * @constant
       */
      _defineProperty(this, "icaltype", "binary");
      this.value = aValue;
    }
    return _createClass(Binary, [{
      key: "decodeValue",
      value:
      /**
       * Base64 decode the current value
       *
       * @return {String}         The base64-decoded value
       */
      function decodeValue() {
        return this._b64_decode(this.value);
      }

      /**
       * Encodes the passed parameter with base64 and sets the internal
       * value to the result.
       *
       * @param {String} aValue      The raw binary value to encode
       */
    }, {
      key: "setEncodedValue",
      value: function setEncodedValue(aValue) {
        this.value = this._b64_encode(aValue);
      }
    }, {
      key: "_b64_encode",
      value: function _b64_encode(data) {
        // http://kevin.vanzonneveld.net
        // +   original by: Tyler Akins (http://rumkin.com)
        // +   improved by: Bayron Guevara
        // +   improved by: Thunder.m
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   bugfixed by: Pellentesque Malesuada
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Rafał Kukawski (http://kukawski.pl)
        // *     example 1: base64_encode('Kevin van Zonneveld');
        // *     returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
        // mozilla has this native
        // - but breaks in 2.0.0.12!
        //if (typeof this.window['atob'] == 'function') {
        //    return atob(data);
        //}
        var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/=";
        var o1,
          o2,
          o3,
          h1,
          h2,
          h3,
          h4,
          bits,
          i = 0,
          ac = 0,
          enc = "",
          tmp_arr = [];
        if (!data) {
          return data;
        }
        do {
          // pack three octets into four hexets
          o1 = data.charCodeAt(i++);
          o2 = data.charCodeAt(i++);
          o3 = data.charCodeAt(i++);
          bits = o1 << 16 | o2 << 8 | o3;
          h1 = bits >> 18 & 0x3f;
          h2 = bits >> 12 & 0x3f;
          h3 = bits >> 6 & 0x3f;
          h4 = bits & 0x3f;

          // use hexets to index into b64, and append result to encoded string
          tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
        } while (i < data.length);
        enc = tmp_arr.join('');
        var r = data.length % 3;
        return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
      }
    }, {
      key: "_b64_decode",
      value: function _b64_decode(data) {
        // http://kevin.vanzonneveld.net
        // +   original by: Tyler Akins (http://rumkin.com)
        // +   improved by: Thunder.m
        // +      input by: Aman Gupta
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   bugfixed by: Onno Marsman
        // +   bugfixed by: Pellentesque Malesuada
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // *     example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
        // *     returns 1: 'Kevin van Zonneveld'
        // mozilla has this native
        // - but breaks in 2.0.0.12!
        //if (typeof this.window['btoa'] == 'function') {
        //    return btoa(data);
        //}
        var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/=";
        var o1,
          o2,
          o3,
          h1,
          h2,
          h3,
          h4,
          bits,
          i = 0,
          ac = 0,
          dec = "",
          tmp_arr = [];
        if (!data) {
          return data;
        }
        data += '';
        do {
          // unpack four hexets into three octets using index points in b64
          h1 = b64.indexOf(data.charAt(i++));
          h2 = b64.indexOf(data.charAt(i++));
          h3 = b64.indexOf(data.charAt(i++));
          h4 = b64.indexOf(data.charAt(i++));
          bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
          o1 = bits >> 16 & 0xff;
          o2 = bits >> 8 & 0xff;
          o3 = bits & 0xff;
          if (h3 == 64) {
            tmp_arr[ac++] = String.fromCharCode(o1);
          } else if (h4 == 64) {
            tmp_arr[ac++] = String.fromCharCode(o1, o2);
          } else {
            tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
          }
        } while (i < data.length);
        dec = tmp_arr.join('');
        return dec;
      }

      /**
       * The string representation of this value
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        return this.value;
      }
    }], [{
      key: "fromString",
      value:
      /**
       * Creates a binary value from the given string.
       *
       * @param {String} aString        The binary value string
       * @return {ICAL.Binary}          The binary value instance
       */
      function fromString(aString) {
        return new Binary(aString);
      }
    }]);
  }();

  var DURATION_LETTERS = /([PDWHMTS]{1,1})/;
  var DATA_PROPS_TO_COPY = ["weeks", "days", "hours", "minutes", "seconds", "isNegative"];

  /**
   * This class represents the "duration" value type, with various calculation
   * and manipulation methods.
   *
   * @class
   * @alias ICAL.Duration
   */
  var Duration = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Duration instance.
     *
     * @param {Object} data               An object with members of the duration
     * @param {Number} data.weeks         Duration in weeks
     * @param {Number} data.days          Duration in days
     * @param {Number} data.hours         Duration in hours
     * @param {Number} data.minutes       Duration in minutes
     * @param {Number} data.seconds       Duration in seconds
     * @param {Boolean} data.isNegative   If true, the duration is negative
     */
    function Duration(data) {
      _classCallCheck(this, Duration);
      /**
       * The weeks in this duration
       * @type {Number}
       * @default 0
       */
      _defineProperty(this, "weeks", 0);
      /**
       * The days in this duration
       * @type {Number}
       * @default 0
       */
      _defineProperty(this, "days", 0);
      /**
       * The days in this duration
       * @type {Number}
       * @default 0
       */
      _defineProperty(this, "hours", 0);
      /**
       * The minutes in this duration
       * @type {Number}
       * @default 0
       */
      _defineProperty(this, "minutes", 0);
      /**
       * The seconds in this duration
       * @type {Number}
       * @default 0
       */
      _defineProperty(this, "seconds", 0);
      /**
       * The seconds in this duration
       * @type {Boolean}
       * @default false
       */
      _defineProperty(this, "isNegative", false);
      /**
       * The class identifier.
       * @constant
       * @type {String}
       * @default "icalduration"
       */
      _defineProperty(this, "icalclass", "icalduration");
      /**
       * The type name, to be used in the jCal object.
       * @constant
       * @type {String}
       * @default "duration"
       */
      _defineProperty(this, "icaltype", "duration");
      this.wrappedJSObject = this;
      this.fromData(data);
    }
    return _createClass(Duration, [{
      key: "clone",
      value:
      /**
       * Returns a clone of the duration object.
       *
       * @return {ICAL.Duration}      The cloned object
       */
      function clone() {
        return Duration.fromData(this);
      }

      /**
       * The duration value expressed as a number of seconds.
       *
       * @return {Number}             The duration value in seconds
       */
    }, {
      key: "toSeconds",
      value: function toSeconds() {
        var seconds = this.seconds + 60 * this.minutes + 3600 * this.hours + 86400 * this.days + 7 * 86400 * this.weeks;
        return this.isNegative ? -seconds : seconds;
      }

      /**
       * Reads the passed seconds value into this duration object. Afterwards,
       * members like {@link ICAL.Duration#days days} and {@link ICAL.Duration#weeks weeks} will be set up
       * accordingly.
       *
       * @param {Number} aSeconds     The duration value in seconds
       * @return {ICAL.Duration}      Returns this instance
       */
    }, {
      key: "fromSeconds",
      value: function fromSeconds(aSeconds) {
        var secs = Math.abs(aSeconds);
        this.isNegative = aSeconds < 0;
        this.days = trunc(secs / 86400);

        // If we have a flat number of weeks, use them.
        if (this.days % 7 == 0) {
          this.weeks = this.days / 7;
          this.days = 0;
        } else {
          this.weeks = 0;
        }
        secs -= (this.days + 7 * this.weeks) * 86400;
        this.hours = trunc(secs / 3600);
        secs -= this.hours * 3600;
        this.minutes = trunc(secs / 60);
        secs -= this.minutes * 60;
        this.seconds = secs;
        return this;
      }

      /**
       * Sets up the current instance using members from the passed data object.
       *
       * @param {Object} aData               An object with members of the duration
       * @param {Number} aData.weeks         Duration in weeks
       * @param {Number} aData.days          Duration in days
       * @param {Number} aData.hours         Duration in hours
       * @param {Number} aData.minutes       Duration in minutes
       * @param {Number} aData.seconds       Duration in seconds
       * @param {Boolean} aData.isNegative   If true, the duration is negative
       */
    }, {
      key: "fromData",
      value: function fromData(aData) {
        for (var _i = 0, _DATA_PROPS_TO_COPY = DATA_PROPS_TO_COPY; _i < _DATA_PROPS_TO_COPY.length; _i++) {
          var prop = _DATA_PROPS_TO_COPY[_i];
          if (aData && prop in aData) {
            this[prop] = aData[prop];
          } else {
            this[prop] = 0;
          }
        }
      }

      /**
       * Resets the duration instance to the default values, i.e. PT0S
       */
    }, {
      key: "reset",
      value: function reset() {
        this.isNegative = false;
        this.weeks = 0;
        this.days = 0;
        this.hours = 0;
        this.minutes = 0;
        this.seconds = 0;
      }

      /**
       * Compares the duration instance with another one.
       *
       * @param {ICAL.Duration} aOther        The instance to compare with
       * @return {Number}                     -1, 0 or 1 for less/equal/greater
       */
    }, {
      key: "compare",
      value: function compare(aOther) {
        var thisSeconds = this.toSeconds();
        var otherSeconds = aOther.toSeconds();
        return (thisSeconds > otherSeconds) - (thisSeconds < otherSeconds);
      }

      /**
       * Normalizes the duration instance. For example, a duration with a value
       * of 61 seconds will be normalized to 1 minute and 1 second.
       */
    }, {
      key: "normalize",
      value: function normalize() {
        this.fromSeconds(this.toSeconds());
      }

      /**
       * The string representation of this duration.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        if (this.toSeconds() == 0) {
          return "PT0S";
        } else {
          var str = "";
          if (this.isNegative) str += "-";
          str += "P";
          if (this.weeks) str += this.weeks + "W";
          if (this.days) str += this.days + "D";
          if (this.hours || this.minutes || this.seconds) {
            str += "T";
            if (this.hours) str += this.hours + "H";
            if (this.minutes) str += this.minutes + "M";
            if (this.seconds) str += this.seconds + "S";
          }
          return str;
        }
      }

      /**
       * The iCalendar string representation of this duration.
       * @return {String}
       */
    }, {
      key: "toICALString",
      value: function toICALString() {
        return this.toString();
      }
    }], [{
      key: "fromSeconds",
      value:
      /**
       * Returns a new ICAL.Duration instance from the passed seconds value.
       *
       * @param {Number} aSeconds       The seconds to create the instance from
       * @return {ICAL.Duration}        The newly created duration instance
       */
      function fromSeconds(aSeconds) {
        return new Duration().fromSeconds(aSeconds);
      }

      /**
       * Checks if the given string is an iCalendar duration value.
       *
       * @param {String} value      The raw ical value
       * @return {Boolean}          True, if the given value is of the
       *                              duration ical type
       */
    }, {
      key: "isValueString",
      value: function isValueString(string) {
        return string[0] === 'P' || string[1] === 'P';
      }

      /**
       * Creates a new {@link ICAL.Duration} instance from the passed string.
       *
       * @param {String} aStr       The string to parse
       * @return {ICAL.Duration}    The created duration instance
       */
    }, {
      key: "fromString",
      value: function fromString(aStr) {
        var pos = 0;
        var dict = Object.create(null);
        var chunks = 0;
        while ((pos = aStr.search(DURATION_LETTERS)) !== -1) {
          var type = aStr[pos];
          var numeric = aStr.slice(0, Math.max(0, pos));
          aStr = aStr.slice(pos + 1);
          chunks += parseDurationChunk(type, numeric, dict);
        }
        if (chunks < 2) {
          // There must be at least a chunk with "P" and some unit chunk
          throw new Error('invalid duration value: Not enough duration components in "' + aStr + '"');
        }
        return new Duration(dict);
      }

      /**
       * Creates a new ICAL.Duration instance from the given data object.
       *
       * @param {Object} aData               An object with members of the duration
       * @param {Number} aData.weeks         Duration in weeks
       * @param {Number} aData.days          Duration in days
       * @param {Number} aData.hours         Duration in hours
       * @param {Number} aData.minutes       Duration in minutes
       * @param {Number} aData.seconds       Duration in seconds
       * @param {Boolean} aData.isNegative   If true, the duration is negative
       * @return {ICAL.Duration}             The createad duration instance
       */
    }, {
      key: "fromData",
      value: function fromData(aData) {
        return new Duration(aData);
      }
    }]);
  }();

  /**
   * Internal helper function to handle a chunk of a duration.
   *
   * @private
   * @param {String} letter type of duration chunk
   * @param {String} number numeric value or -/+
   * @param {Object} dict target to assign values to
   */
  function parseDurationChunk(letter, number, object) {
    var type;
    switch (letter) {
      case 'P':
        if (number && number === '-') {
          object.isNegative = true;
        } else {
          object.isNegative = false;
        }
        // period
        break;
      case 'D':
        type = 'days';
        break;
      case 'W':
        type = 'weeks';
        break;
      case 'H':
        type = 'hours';
        break;
      case 'M':
        type = 'minutes';
        break;
      case 'S':
        type = 'seconds';
        break;
      default:
        // Not a valid chunk
        return 0;
    }
    if (type) {
      if (!number && number !== 0) {
        throw new Error('invalid duration value: Missing number before "' + letter + '"');
      }
      var num = parseInt(number, 10);
      if (isStrictlyNaN(num)) {
        throw new Error('invalid duration value: Invalid number "' + number + '" before "' + letter + '"');
      }
      object[type] = num;
    }
    return 1;
  }

  var _Time;

  /**
   * @classdesc
   * iCalendar Time representation (similar to JS Date object).  Fully
   * independent of system (OS) timezone / time.  Unlike JS Date, the month
   * January is 1, not zero.
   *
   * @example
   * var time = new ICAL.Time({
   *   year: 2012,
   *   month: 10,
   *   day: 11
   *   minute: 0,
   *   second: 0,
   *   isDate: false
   * });
   *
   *
   * @alias ICAL.Time
   * @class
  */
  var Time = /*#__PURE__*/function () {
    // MONDAY

    /**
     * Creates a new ICAL.Time instance.
     *
     * @param {Object} data           Time initialization
     * @param {Number=} data.year     The year for this date
     * @param {Number=} data.month    The month for this date
     * @param {Number=} data.day      The day for this date
     * @param {Number=} data.hour     The hour for this date
     * @param {Number=} data.minute   The minute for this date
     * @param {Number=} data.second   The second for this date
     * @param {Boolean=} data.isDate  If true, the instance represents a date (as
     *                                  opposed to a date-time)
     * @param {ICAL.Timezone} zone timezone this position occurs in
     */
    function Time(data, zone) {
      _classCallCheck(this, Time);
      /**
       * The class identifier.
       * @constant
       * @type {String}
       * @default "icaltime"
       */
      _defineProperty(this, "icalclass", "icaltime");
      _defineProperty(this, "_cachedUnixTime", null);
      /**
       * The timezone for this time.
       * @type {ICAL.Timezone}
       */
      _defineProperty(this, "zone", null);
      /**
       * Internal uses to indicate that a change has been made and the next read
       * operation must attempt to normalize the value (for example changing the
       * day to 33).
       *
       * @type {Boolean}
       * @private
       */
      _defineProperty(this, "_pendingNormalization", false);
      this.wrappedJSObject = this;
      var time = this._time = Object.create(null);

      /* time defaults */
      time.year = 0;
      time.month = 1;
      time.day = 1;
      time.hour = 0;
      time.minute = 0;
      time.second = 0;
      time.isDate = false;
      this.fromData(data, zone);
    }
    return _createClass(Time, [{
      key: "icaltype",
      get:
      /**
       * The type name, to be used in the jCal object. This value may change and
       * is strictly defined by the {@link ICAL.Time#isDate isDate} member.
       * @readonly
       * @type {String}
       * @default "date-time"
       */
      function get() {
        return this.isDate ? 'date' : 'date-time';
      }
    }, {
      key: "clone",
      value:
      /**
       * Returns a clone of the time object.
       *
       * @return {ICAL.Time}              The cloned object
       */
      function clone() {
        return new Time(this._time, this.zone);
      }

      /**
       * Reset the time instance to epoch time
       */
    }, {
      key: "reset",
      value: function reset() {
        this.fromData(Time.epochTime);
        this.zone = Timezone.utcTimezone;
      }

      /**
       * Reset the time instance to the given date/time values.
       *
       * @param {Number} year             The year to set
       * @param {Number} month            The month to set
       * @param {Number} day              The day to set
       * @param {Number} hour             The hour to set
       * @param {Number} minute           The minute to set
       * @param {Number} second           The second to set
       * @param {ICAL.Timezone} timezone  The timezone to set
       */
    }, {
      key: "resetTo",
      value: function resetTo(year, month, day, hour, minute, second, timezone) {
        this.fromData({
          year: year,
          month: month,
          day: day,
          hour: hour,
          minute: minute,
          second: second,
          zone: timezone
        });
      }

      /**
       * Set up the current instance from the Javascript date value.
       *
       * @param {?Date} aDate     The Javascript Date to read, or null to reset
       * @param {Boolean} useUTC  If true, the UTC values of the date will be used
       */
    }, {
      key: "fromJSDate",
      value: function fromJSDate(aDate, useUTC) {
        if (!aDate) {
          this.reset();
        } else {
          if (useUTC) {
            this.zone = Timezone.utcTimezone;
            this.year = aDate.getUTCFullYear();
            this.month = aDate.getUTCMonth() + 1;
            this.day = aDate.getUTCDate();
            this.hour = aDate.getUTCHours();
            this.minute = aDate.getUTCMinutes();
            this.second = aDate.getUTCSeconds();
          } else {
            this.zone = Timezone.localTimezone;
            this.year = aDate.getFullYear();
            this.month = aDate.getMonth() + 1;
            this.day = aDate.getDate();
            this.hour = aDate.getHours();
            this.minute = aDate.getMinutes();
            this.second = aDate.getSeconds();
          }
        }
        this._cachedUnixTime = null;
        return this;
      }

      /**
       * Sets up the current instance using members from the passed data object.
       *
       * @param {Object} aData            Time initialization
       * @param {Number=} aData.year      The year for this date
       * @param {Number=} aData.month     The month for this date
       * @param {Number=} aData.day       The day for this date
       * @param {Number=} aData.hour      The hour for this date
       * @param {Number=} aData.minute    The minute for this date
       * @param {Number=} aData.second    The second for this date
       * @param {Boolean=} aData.isDate   If true, the instance represents a date
       *                                    (as opposed to a date-time)
       * @param {ICAL.Timezone=} aZone    Timezone this position occurs in
       */
    }, {
      key: "fromData",
      value: function fromData(aData, aZone) {
        if (aData) {
          for (var _i = 0, _Object$entries = Object.entries(aData); _i < _Object$entries.length; _i++) {
            var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
              key = _Object$entries$_i[0],
              value = _Object$entries$_i[1];
            // ical type cannot be set
            if (key === 'icaltype') continue;
            this[key] = value;
          }
        }
        if (aZone) {
          this.zone = aZone;
        }
        if (aData && !("isDate" in aData)) {
          this.isDate = !("hour" in aData);
        } else if (aData && "isDate" in aData) {
          this.isDate = aData.isDate;
        }
        if (aData && "timezone" in aData) {
          var zone = TimezoneService.get(aData.timezone);
          this.zone = zone || Timezone.localTimezone;
        }
        if (aData && "zone" in aData) {
          this.zone = aData.zone;
        }
        if (!this.zone) {
          this.zone = Timezone.localTimezone;
        }
        this._cachedUnixTime = null;
        return this;
      }

      /**
       * Calculate the day of week.
       * @param {ICAL.Time.weekDay=} aWeekStart
       *        The week start weekday, defaults to SUNDAY
       * @return {ICAL.Time.weekDay}
       */
    }, {
      key: "dayOfWeek",
      value: function dayOfWeek(aWeekStart) {
        var firstDow = aWeekStart || Time.SUNDAY;
        var dowCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + firstDow;
        if (dowCacheKey in Time._dowCache) {
          return Time._dowCache[dowCacheKey];
        }

        // Using Zeller's algorithm
        var q = this.day;
        var m = this.month + (this.month < 3 ? 12 : 0);
        var Y = this.year - (this.month < 3 ? 1 : 0);
        var h = q + Y + trunc((m + 1) * 26 / 10) + trunc(Y / 4);
        {
          // eslint-disable-line no-constant-condition
          h += trunc(Y / 100) * 6 + trunc(Y / 400);
        }

        // Normalize to 1 = wkst
        h = (h + 7 - firstDow) % 7 + 1;
        Time._dowCache[dowCacheKey] = h;
        return h;
      }

      /**
       * Calculate the day of year.
       * @return {Number}
       */
    }, {
      key: "dayOfYear",
      value: function dayOfYear() {
        var is_leap = Time.isLeapYear(this.year) ? 1 : 0;
        var diypm = Time.daysInYearPassedMonth;
        return diypm[is_leap][this.month - 1] + this.day;
      }

      /**
       * Returns a copy of the current date/time, rewound to the start of the
       * week. The resulting ICAL.Time instance is of icaltype date, even if this
       * is a date-time.
       *
       * @param {ICAL.Time.weekDay=} aWeekStart
       *        The week start weekday, defaults to SUNDAY
       * @return {ICAL.Time}      The start of the week (cloned)
       */
    }, {
      key: "startOfWeek",
      value: function startOfWeek(aWeekStart) {
        var firstDow = aWeekStart || Time.SUNDAY;
        var result = this.clone();
        result.day -= (this.dayOfWeek() + 7 - firstDow) % 7;
        result.isDate = true;
        result.hour = 0;
        result.minute = 0;
        result.second = 0;
        return result;
      }

      /**
       * Returns a copy of the current date/time, shifted to the end of the week.
       * The resulting ICAL.Time instance is of icaltype date, even if this is a
       * date-time.
       *
       * @param {ICAL.Time.weekDay=} aWeekStart
       *        The week start weekday, defaults to SUNDAY
       * @return {ICAL.Time}      The end of the week (cloned)
       */
    }, {
      key: "endOfWeek",
      value: function endOfWeek(aWeekStart) {
        var firstDow = aWeekStart || Time.SUNDAY;
        var result = this.clone();
        result.day += (7 - this.dayOfWeek() + firstDow - Time.SUNDAY) % 7;
        result.isDate = true;
        result.hour = 0;
        result.minute = 0;
        result.second = 0;
        return result;
      }

      /**
       * Returns a copy of the current date/time, rewound to the start of the
       * month. The resulting ICAL.Time instance is of icaltype date, even if
       * this is a date-time.
       *
       * @return {ICAL.Time}      The start of the month (cloned)
       */
    }, {
      key: "startOfMonth",
      value: function startOfMonth() {
        var result = this.clone();
        result.day = 1;
        result.isDate = true;
        result.hour = 0;
        result.minute = 0;
        result.second = 0;
        return result;
      }

      /**
       * Returns a copy of the current date/time, shifted to the end of the
       * month.  The resulting ICAL.Time instance is of icaltype date, even if
       * this is a date-time.
       *
       * @return {ICAL.Time}      The end of the month (cloned)
       */
    }, {
      key: "endOfMonth",
      value: function endOfMonth() {
        var result = this.clone();
        result.day = Time.daysInMonth(result.month, result.year);
        result.isDate = true;
        result.hour = 0;
        result.minute = 0;
        result.second = 0;
        return result;
      }

      /**
       * Returns a copy of the current date/time, rewound to the start of the
       * year. The resulting ICAL.Time instance is of icaltype date, even if
       * this is a date-time.
       *
       * @return {ICAL.Time}      The start of the year (cloned)
       */
    }, {
      key: "startOfYear",
      value: function startOfYear() {
        var result = this.clone();
        result.day = 1;
        result.month = 1;
        result.isDate = true;
        result.hour = 0;
        result.minute = 0;
        result.second = 0;
        return result;
      }

      /**
       * Returns a copy of the current date/time, shifted to the end of the
       * year.  The resulting ICAL.Time instance is of icaltype date, even if
       * this is a date-time.
       *
       * @return {ICAL.Time}      The end of the year (cloned)
       */
    }, {
      key: "endOfYear",
      value: function endOfYear() {
        var result = this.clone();
        result.day = 31;
        result.month = 12;
        result.isDate = true;
        result.hour = 0;
        result.minute = 0;
        result.second = 0;
        return result;
      }

      /**
       * First calculates the start of the week, then returns the day of year for
       * this date. If the day falls into the previous year, the day is zero or negative.
       *
       * @param {ICAL.Time.weekDay=} aFirstDayOfWeek
       *        The week start weekday, defaults to SUNDAY
       * @return {Number}     The calculated day of year
       */
    }, {
      key: "startDoyWeek",
      value: function startDoyWeek(aFirstDayOfWeek) {
        var firstDow = aFirstDayOfWeek || Time.SUNDAY;
        var delta = this.dayOfWeek() - firstDow;
        if (delta < 0) delta += 7;
        return this.dayOfYear() - delta;
      }

      /**
       * Get the dominical letter for the current year. Letters range from A - G
       * for common years, and AG to GF for leap years.
       *
       * @param {Number} yr           The year to retrieve the letter for
       * @return {String}             The dominical letter.
       */
    }, {
      key: "getDominicalLetter",
      value: function getDominicalLetter() {
        return Time.getDominicalLetter(this.year);
      }

      /**
       * Finds the nthWeekDay relative to the current month (not day).  The
       * returned value is a day relative the month that this month belongs to so
       * 1 would indicate the first of the month and 40 would indicate a day in
       * the following month.
       *
       * @param {Number} aDayOfWeek   Day of the week see the day name constants
       * @param {Number} aPos         Nth occurrence of a given week day values
       *        of 1 and 0 both indicate the first weekday of that type. aPos may
       *        be either positive or negative
       *
       * @return {Number} numeric value indicating a day relative
       *                   to the current month of this time object
       */
    }, {
      key: "nthWeekDay",
      value: function nthWeekDay(aDayOfWeek, aPos) {
        var daysInMonth = Time.daysInMonth(this.month, this.year);
        var weekday;
        var pos = aPos;
        var start = 0;
        var otherDay = this.clone();
        if (pos >= 0) {
          otherDay.day = 1;

          // because 0 means no position has been given
          // 1 and 0 indicate the same day.
          if (pos != 0) {
            // remove the extra numeric value
            pos--;
          }

          // set current start offset to current day.
          start = otherDay.day;

          // find the current day of week
          var startDow = otherDay.dayOfWeek();

          // calculate the difference between current
          // day of the week and desired day of the week
          var offset = aDayOfWeek - startDow;

          // if the offset goes into the past
          // week we add 7 so it goes into the next
          // week. We only want to go forward in time here.
          if (offset < 0)
            // this is really important otherwise we would
            // end up with dates from in the past.
            offset += 7;

          // add offset to start so start is the same
          // day of the week as the desired day of week.
          start += offset;

          // because we are going to add (and multiply)
          // the numeric value of the day we subtract it
          // from the start position so not to add it twice.
          start -= aDayOfWeek;

          // set week day
          weekday = aDayOfWeek;
        } else {
          // then we set it to the last day in the current month
          otherDay.day = daysInMonth;

          // find the ends weekday
          var endDow = otherDay.dayOfWeek();
          pos++;
          weekday = endDow - aDayOfWeek;
          if (weekday < 0) {
            weekday += 7;
          }
          weekday = daysInMonth - weekday;
        }
        weekday += pos * 7;
        return start + weekday;
      }

      /**
       * Checks if current time is the nth weekday, relative to the current
       * month.  Will always return false when rule resolves outside of current
       * month.
       *
       * @param {ICAL.Time.weekDay} aDayOfWeek       Day of week to check
       * @param {Number} aPos                        Relative position
       * @return {Boolean}                           True, if it is the nth weekday
       */
    }, {
      key: "isNthWeekDay",
      value: function isNthWeekDay(aDayOfWeek, aPos) {
        var dow = this.dayOfWeek();
        if (aPos === 0 && dow === aDayOfWeek) {
          return true;
        }

        // get pos
        var day = this.nthWeekDay(aDayOfWeek, aPos);
        if (day === this.day) {
          return true;
        }
        return false;
      }

      /**
       * Calculates the ISO 8601 week number. The first week of a year is the
       * week that contains the first Thursday. The year can have 53 weeks, if
       * January 1st is a Friday.
       *
       * Note there are regions where the first week of the year is the one that
       * starts on January 1st, which may offset the week number. Also, if a
       * different week start is specified, this will also affect the week
       * number.
       *
       * @see ICAL.Time.weekOneStarts
       * @param {ICAL.Time.weekDay} aWeekStart        The weekday the week starts with
       * @return {Number}                             The ISO week number
       */
    }, {
      key: "weekNumber",
      value: function weekNumber(aWeekStart) {
        var wnCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + aWeekStart;
        if (wnCacheKey in Time._wnCache) {
          return Time._wnCache[wnCacheKey];
        }
        // This function courtesty of Julian Bucknall, published under the MIT license
        // http://www.boyet.com/articles/publishedarticles/calculatingtheisoweeknumb.html
        // plus some fixes to be able to use different week starts.
        var week1;
        var dt = this.clone();
        dt.isDate = true;
        var isoyear = this.year;
        if (dt.month == 12 && dt.day > 25) {
          week1 = Time.weekOneStarts(isoyear + 1, aWeekStart);
          if (dt.compare(week1) < 0) {
            week1 = Time.weekOneStarts(isoyear, aWeekStart);
          } else {
            isoyear++;
          }
        } else {
          week1 = Time.weekOneStarts(isoyear, aWeekStart);
          if (dt.compare(week1) < 0) {
            week1 = Time.weekOneStarts(--isoyear, aWeekStart);
          }
        }
        var daysBetween = dt.subtractDate(week1).toSeconds() / 86400;
        var answer = trunc(daysBetween / 7) + 1;
        Time._wnCache[wnCacheKey] = answer;
        return answer;
      }

      /**
       * Adds the duration to the current time. The instance is modified in
       * place.
       *
       * @param {ICAL.Duration} aDuration         The duration to add
       */
    }, {
      key: "addDuration",
      value: function addDuration(aDuration) {
        var mult = aDuration.isNegative ? -1 : 1;

        // because of the duration optimizations it is much
        // more efficient to grab all the values up front
        // then set them directly (which will avoid a normalization call).
        // So we don't actually normalize until we need it.
        var second = this.second;
        var minute = this.minute;
        var hour = this.hour;
        var day = this.day;
        second += mult * aDuration.seconds;
        minute += mult * aDuration.minutes;
        hour += mult * aDuration.hours;
        day += mult * aDuration.days;
        day += mult * 7 * aDuration.weeks;
        this.second = second;
        this.minute = minute;
        this.hour = hour;
        this.day = day;
        this._cachedUnixTime = null;
      }

      /**
       * Subtract the date details (_excluding_ timezone).  Useful for finding
       * the relative difference between two time objects excluding their
       * timezone differences.
       *
       * @param {ICAL.Time} aDate     The date to subtract
       * @return {ICAL.Duration}      The difference as a duration
       */
    }, {
      key: "subtractDate",
      value: function subtractDate(aDate) {
        var unixTime = this.toUnixTime() + this.utcOffset();
        var other = aDate.toUnixTime() + aDate.utcOffset();
        return Duration.fromSeconds(unixTime - other);
      }

      /**
       * Subtract the date details, taking timezones into account.
       *
       * @param {ICAL.Time} aDate  The date to subtract
       * @return {ICAL.Duration}  The difference in duration
       */
    }, {
      key: "subtractDateTz",
      value: function subtractDateTz(aDate) {
        var unixTime = this.toUnixTime();
        var other = aDate.toUnixTime();
        return Duration.fromSeconds(unixTime - other);
      }

      /**
       * Compares the ICAL.Time instance with another one.
       *
       * @param {ICAL.Duration} aOther        The instance to compare with
       * @return {Number}                     -1, 0 or 1 for less/equal/greater
       */
    }, {
      key: "compare",
      value: function compare(other) {
        var a = this.toUnixTime();
        var b = other.toUnixTime();
        if (a > b) return 1;
        if (b > a) return -1;
        return 0;
      }

      /**
       * Compares only the date part of this instance with another one.
       *
       * @param {ICAL.Duration} other         The instance to compare with
       * @param {ICAL.Timezone} tz            The timezone to compare in
       * @return {Number}                     -1, 0 or 1 for less/equal/greater
       */
    }, {
      key: "compareDateOnlyTz",
      value: function compareDateOnlyTz(other, tz) {
        var a = this.convertToZone(tz);
        var b = other.convertToZone(tz);
        var rc = 0;
        if ((rc = Time._cmp_attr(a, b, "year")) != 0) return rc;
        if ((rc = Time._cmp_attr(a, b, "month")) != 0) return rc;
        if ((rc = Time._cmp_attr(a, b, "day")) != 0) return rc;
        return rc;
      }

      /**
       * Convert the instance into another timezone. The returned ICAL.Time
       * instance is always a copy.
       *
       * @param {ICAL.Timezone} zone      The zone to convert to
       * @return {ICAL.Time}              The copy, converted to the zone
       */
    }, {
      key: "convertToZone",
      value: function convertToZone(zone) {
        var copy = this.clone();
        var zone_equals = this.zone.tzid == zone.tzid;
        if (!this.isDate && !zone_equals) {
          Timezone.convert_time(copy, this.zone, zone);
        }
        copy.zone = zone;
        return copy;
      }

      /**
       * Calculates the UTC offset of the current date/time in the timezone it is
       * in.
       *
       * @return {Number}     UTC offset in seconds
       */
    }, {
      key: "utcOffset",
      value: function utcOffset() {
        if (this.zone == Timezone.localTimezone || this.zone == Timezone.utcTimezone) {
          return 0;
        } else {
          return this.zone.utcOffset(this);
        }
      }

      /**
       * Returns an RFC 5545 compliant ical representation of this object.
       *
       * @return {String} ical date/date-time
       */
    }, {
      key: "toICALString",
      value: function toICALString() {
        var string = this.toString();
        if (string.length > 10) {
          return design.icalendar.value['date-time'].toICAL(string);
        } else {
          return design.icalendar.value.date.toICAL(string);
        }
      }

      /**
       * The string representation of this date/time, in jCal form
       * (including : and - separators).
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        var result = this.year + '-' + pad2(this.month) + '-' + pad2(this.day);
        if (!this.isDate) {
          result += 'T' + pad2(this.hour) + ':' + pad2(this.minute) + ':' + pad2(this.second);
          if (this.zone === Timezone.utcTimezone) {
            result += 'Z';
          }
        }
        return result;
      }

      /**
       * Converts the current instance to a Javascript date
       * @return {Date}
       */
    }, {
      key: "toJSDate",
      value: function toJSDate() {
        if (this.zone == Timezone.localTimezone) {
          if (this.isDate) {
            return new Date(this.year, this.month - 1, this.day);
          } else {
            return new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, 0);
          }
        } else {
          return new Date(this.toUnixTime() * 1000);
        }
      }
    }, {
      key: "_normalize",
      value: function _normalize() {
        if (this._time.isDate) {
          this._time.hour = 0;
          this._time.minute = 0;
          this._time.second = 0;
        }
        this.adjust(0, 0, 0, 0);
        return this;
      }

      /**
       * Adjust the date/time by the given offset
       *
       * @param {Number} aExtraDays       The extra amount of days
       * @param {Number} aExtraHours      The extra amount of hours
       * @param {Number} aExtraMinutes    The extra amount of minutes
       * @param {Number} aExtraSeconds    The extra amount of seconds
       * @param {Number=} aTime           The time to adjust, defaults to the
       *                                    current instance.
       */
    }, {
      key: "adjust",
      value: function adjust(aExtraDays, aExtraHours, aExtraMinutes, aExtraSeconds, aTime) {
        var minutesOverflow,
          hoursOverflow,
          daysOverflow = 0,
          yearsOverflow = 0;
        var second, minute, hour, day;
        var daysInMonth;
        var time = aTime || this._time;
        if (!time.isDate) {
          second = time.second + aExtraSeconds;
          time.second = second % 60;
          minutesOverflow = trunc(second / 60);
          if (time.second < 0) {
            time.second += 60;
            minutesOverflow--;
          }
          minute = time.minute + aExtraMinutes + minutesOverflow;
          time.minute = minute % 60;
          hoursOverflow = trunc(minute / 60);
          if (time.minute < 0) {
            time.minute += 60;
            hoursOverflow--;
          }
          hour = time.hour + aExtraHours + hoursOverflow;
          time.hour = hour % 24;
          daysOverflow = trunc(hour / 24);
          if (time.hour < 0) {
            time.hour += 24;
            daysOverflow--;
          }
        }

        // Adjust month and year first, because we need to know what month the day
        // is in before adjusting it.
        if (time.month > 12) {
          yearsOverflow = trunc((time.month - 1) / 12);
        } else if (time.month < 1) {
          yearsOverflow = trunc(time.month / 12) - 1;
        }
        time.year += yearsOverflow;
        time.month -= 12 * yearsOverflow;

        // Now take care of the days (and adjust month if needed)
        day = time.day + aExtraDays + daysOverflow;
        if (day > 0) {
          for (;;) {
            daysInMonth = Time.daysInMonth(time.month, time.year);
            if (day <= daysInMonth) {
              break;
            }
            time.month++;
            if (time.month > 12) {
              time.year++;
              time.month = 1;
            }
            day -= daysInMonth;
          }
        } else {
          while (day <= 0) {
            if (time.month == 1) {
              time.year--;
              time.month = 12;
            } else {
              time.month--;
            }
            day += Time.daysInMonth(time.month, time.year);
          }
        }
        time.day = day;
        this._cachedUnixTime = null;
        return this;
      }

      /**
       * Sets up the current instance from unix time, the number of seconds since
       * January 1st, 1970.
       *
       * @param {Number} seconds      The seconds to set up with
       */
    }, {
      key: "fromUnixTime",
      value: function fromUnixTime(seconds) {
        this.zone = Timezone.utcTimezone;
        // We could use `fromJSDate` here, but this is about twice as fast.
        // We could also clone `epochTime` and use `adjust` for a more
        // ical.js-centric approach, but this is about 100 times as fast.
        var date = new Date(seconds * 1000);
        this.year = date.getUTCFullYear();
        this.month = date.getUTCMonth() + 1;
        this.day = date.getUTCDate();
        if (this._time.isDate) {
          this.hour = 0;
          this.minute = 0;
          this.second = 0;
        } else {
          this.hour = date.getUTCHours();
          this.minute = date.getUTCMinutes();
          this.second = date.getUTCSeconds();
        }
        this._cachedUnixTime = null;
      }

      /**
       * Converts the current instance to seconds since January 1st 1970.
       *
       * @return {Number}         Seconds since 1970
       */
    }, {
      key: "toUnixTime",
      value: function toUnixTime() {
        if (this._cachedUnixTime !== null) {
          return this._cachedUnixTime;
        }
        var offset = this.utcOffset();

        // we use the offset trick to ensure
        // that we are getting the actual UTC time
        var ms = Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second - offset);

        // seconds
        this._cachedUnixTime = ms / 1000;
        return this._cachedUnixTime;
      }

      /**
       * Converts time to into Object which can be serialized then re-created
       * using the constructor.
       *
       * @example
       * // toJSON will automatically be called
       * var json = JSON.stringify(mytime);
       *
       * var deserialized = JSON.parse(json);
       *
       * var time = new ICAL.Time(deserialized);
       *
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        var copy = ['year', 'month', 'day', 'hour', 'minute', 'second', 'isDate'];
        var result = Object.create(null);
        var i = 0;
        var len = copy.length;
        var prop;
        for (; i < len; i++) {
          prop = copy[i];
          result[prop] = this[prop];
        }
        if (this.zone) {
          result.timezone = this.zone.tzid;
        }
        return result;
      }
    }], [{
      key: "daysInMonth",
      value:
      /**
       * Returns the days in the given month
       *
       * @param {Number} month      The month to check
       * @param {Number} year       The year to check
       * @return {Number}           The number of days in the month
       */
      function daysInMonth(month, year) {
        var _daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        var days = 30;
        if (month < 1 || month > 12) return days;
        days = _daysInMonth[month];
        if (month == 2) {
          days += Time.isLeapYear(year);
        }
        return days;
      }

      /**
       * Checks if the year is a leap year
       *
       * @param {Number} year       The year to check
       * @return {Boolean}          True, if the year is a leap year
       */
    }, {
      key: "isLeapYear",
      value: function isLeapYear(year) {
        if (year <= 1752) {
          return year % 4 == 0;
        } else {
          return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
        }
      }

      /**
       * Create a new ICAL.Time from the day of year and year. The date is returned
       * in floating timezone.
       *
       * @param {Number} aDayOfYear     The day of year
       * @param {Number} aYear          The year to create the instance in
       * @return {ICAL.Time}            The created instance with the calculated date
       */
    }, {
      key: "fromDayOfYear",
      value: function fromDayOfYear(aDayOfYear, aYear) {
        var year = aYear;
        var doy = aDayOfYear;
        var tt = new Time();
        tt.auto_normalize = false;
        var is_leap = Time.isLeapYear(year) ? 1 : 0;
        if (doy < 1) {
          year--;
          is_leap = Time.isLeapYear(year) ? 1 : 0;
          doy += Time.daysInYearPassedMonth[is_leap][12];
          return Time.fromDayOfYear(doy, year);
        } else if (doy > Time.daysInYearPassedMonth[is_leap][12]) {
          is_leap = Time.isLeapYear(year) ? 1 : 0;
          doy -= Time.daysInYearPassedMonth[is_leap][12];
          year++;
          return Time.fromDayOfYear(doy, year);
        }
        tt.year = year;
        tt.isDate = true;
        for (var month = 11; month >= 0; month--) {
          if (doy > Time.daysInYearPassedMonth[is_leap][month]) {
            tt.month = month + 1;
            tt.day = doy - Time.daysInYearPassedMonth[is_leap][month];
            break;
          }
        }
        tt.auto_normalize = true;
        return tt;
      }

      /**
       * Returns a new ICAL.Time instance from a date string, e.g 2015-01-02.
       *
       * @deprecated                Use {@link ICAL.Time.fromDateString} instead
       * @param {String} str        The string to create from
       * @return {ICAL.Time}        The date/time instance
       */
    }, {
      key: "fromStringv2",
      value: function fromStringv2(str) {
        return new Time({
          year: parseInt(str.slice(0, 4), 10),
          month: parseInt(str.slice(5, 7), 10),
          day: parseInt(str.slice(8, 10), 10),
          isDate: true
        });
      }

      /**
       * Returns a new ICAL.Time instance from a date string, e.g 2015-01-02.
       *
       * @param {String} aValue     The string to create from
       * @return {ICAL.Time}        The date/time instance
       */
    }, {
      key: "fromDateString",
      value: function fromDateString(aValue) {
        // Dates should have no timezone.
        // Google likes to sometimes specify Z on dates
        // we specifically ignore that to avoid issues.

        // YYYY-MM-DD
        // 2012-10-10
        return new Time({
          year: strictParseInt(aValue.slice(0, 4)),
          month: strictParseInt(aValue.slice(5, 7)),
          day: strictParseInt(aValue.slice(8, 10)),
          isDate: true
        });
      }

      /**
       * Returns a new ICAL.Time instance from a date-time string, e.g
       * 2015-01-02T03:04:05. If a property is specified, the timezone is set up
       * from the property's TZID parameter.
       *
       * @param {String} aValue         The string to create from
       * @param {ICAL.Property=} prop   The property the date belongs to
       * @return {ICAL.Time}            The date/time instance
       */
    }, {
      key: "fromDateTimeString",
      value: function fromDateTimeString(aValue, prop) {
        if (aValue.length < 19) {
          throw new Error('invalid date-time value: "' + aValue + '"');
        }
        var zone;
        var zoneId;
        if (aValue[19] && aValue[19] === 'Z') {
          zone = Timezone.utcTimezone;
        } else if (prop) {
          zoneId = prop.getParameter('tzid');
          if (prop.parent) {
            if (prop.parent.name === 'standard' || prop.parent.name === 'daylight') {
              // Per RFC 5545 3.8.2.4 and 3.8.2.2, start/end date-times within
              // these components MUST be specified in local time.
              zone = Timezone.localTimezone;
            } else if (zoneId) {
              // If the desired time zone is defined within the component tree,
              // fetch its definition and prefer that.
              zone = prop.parent.getTimeZoneByID(zoneId);
            }
          }
        }
        var timeData = {
          year: strictParseInt(aValue.slice(0, 4)),
          month: strictParseInt(aValue.slice(5, 7)),
          day: strictParseInt(aValue.slice(8, 10)),
          hour: strictParseInt(aValue.slice(11, 13)),
          minute: strictParseInt(aValue.slice(14, 16)),
          second: strictParseInt(aValue.slice(17, 19))
        };

        // Although RFC 5545 requires that all TZIDs used within a file have a
        // corresponding time zone definition, we may not be parsing the full file
        // or we may be dealing with a non-compliant file; in either case, we can
        // check our own time zone service for the TZID in a last-ditch effort.
        if (zoneId && !zone) {
          timeData.timezone = zoneId;
        }

        // 2012-10-10T10:10:10(Z)?
        return new Time(timeData, zone);
      }

      /**
       * Returns a new ICAL.Time instance from a date or date-time string,
       *
       * @param {String} aValue         The string to create from
       * @param {ICAL.Property=} prop   The property the date belongs to
       * @return {ICAL.Time}            The date/time instance
       */
    }, {
      key: "fromString",
      value: function fromString(aValue, aProperty) {
        if (aValue.length > 10) {
          return Time.fromDateTimeString(aValue, aProperty);
        } else {
          return Time.fromDateString(aValue);
        }
      }

      /**
       * Creates a new ICAL.Time instance from the given Javascript Date.
       *
       * @param {?Date} aDate     The Javascript Date to read, or null to reset
       * @param {Boolean} useUTC  If true, the UTC values of the date will be used
       */
    }, {
      key: "fromJSDate",
      value: function fromJSDate(aDate, useUTC) {
        var tt = new Time();
        return tt.fromJSDate(aDate, useUTC);
      }

      /**
       * Creates a new ICAL.Time instance from the the passed data object.
       *
       * @param {Object} aData            Time initialization
       * @param {Number=} aData.year      The year for this date
       * @param {Number=} aData.month     The month for this date
       * @param {Number=} aData.day       The day for this date
       * @param {Number=} aData.hour      The hour for this date
       * @param {Number=} aData.minute    The minute for this date
       * @param {Number=} aData.second    The second for this date
       * @param {Boolean=} aData.isDate   If true, the instance represents a date
       *                                    (as opposed to a date-time)
       * @param {ICAL.Timezone=} aZone    Timezone this position occurs in
       */
    }, {
      key: "now",
      value:
      /**
       * Creates a new ICAL.Time instance from the current moment.
       * The instance is “floating” - has no timezone relation.
       * To create an instance considering the time zone, call
       * ICAL.Time.fromJSDate(new Date(), true)
       * @return {ICAL.Time}
       */
      function now() {
        return Time.fromJSDate(new Date(), false);
      }

      /**
       * Returns the date on which ISO week number 1 starts.
       *
       * @see ICAL.Time#weekNumber
       * @param {Number} aYear                  The year to search in
       * @param {ICAL.Time.weekDay=} aWeekStart The week start weekday, used for calculation.
       * @return {ICAL.Time}                    The date on which week number 1 starts
       */
    }, {
      key: "weekOneStarts",
      value: function weekOneStarts(aYear, aWeekStart) {
        var t = Time.fromData({
          year: aYear,
          month: 1,
          day: 1,
          isDate: true
        });
        var dow = t.dayOfWeek();
        var wkst = aWeekStart || Time.DEFAULT_WEEK_START;
        if (dow > Time.THURSDAY) {
          t.day += 7;
        }
        if (wkst > Time.THURSDAY) {
          t.day -= 7;
        }
        t.day -= dow - wkst;
        return t;
      }

      /**
       * Get the dominical letter for the given year. Letters range from A - G for
       * common years, and AG to GF for leap years.
       *
       * @param {Number} yr           The year to retrieve the letter for
       * @return {String}             The dominical letter.
       */
    }, {
      key: "getDominicalLetter",
      value: function getDominicalLetter(yr) {
        var LTRS = "GFEDCBA";
        var dom = (yr + (yr / 4 | 0) + (yr / 400 | 0) - (yr / 100 | 0) - 1) % 7;
        var isLeap = Time.isLeapYear(yr);
        if (isLeap) {
          return LTRS[(dom + 6) % 7] + LTRS[dom];
        } else {
          return LTRS[dom];
        }
      }
    }, {
      key: "epochTime",
      get:
      /**
       * January 1st, 1970 as an ICAL.Time.
       * @type {ICAL.Time}
       * @constant
       * @instance
       */
      function get() {
        if (!_assertClassBrand(Time, this, _epochTime)._) {
          _epochTime._ = _assertClassBrand(Time, this, Time.fromData({
            year: 1970,
            month: 1,
            day: 1,
            hour: 0,
            minute: 0,
            second: 0,
            isDate: false,
            timezone: "Z"
          }));
        }
        return _assertClassBrand(Time, this, _epochTime)._;
      }
    }, {
      key: "_cmp_attr",
      value: function _cmp_attr(a, b, attr) {
        if (a[attr] > b[attr]) return 1;
        if (a[attr] < b[attr]) return -1;
        return 0;
      }

      /**
       * The days that have passed in the year after a given month. The array has
       * two members, one being an array of passed days for non-leap years, the
       * other analog for leap years.
       * @example
       * var isLeapYear = ICAL.Time.isLeapYear(year);
       * var passedDays = ICAL.Time.daysInYearPassedMonth[isLeapYear][month];
       * @type {Array.<Array.<Number>>}
       */
    }]);
  }();
  _Time = Time;
  _defineProperty(Time, "_dowCache", {});
  _defineProperty(Time, "_wnCache", {});
  _defineProperty(Time, "fromData", function fromData(aData, aZone) {
    var t = new _Time();
    return t.fromData(aData, aZone);
  });
  var _epochTime = {
    _: null
  };
  _defineProperty(Time, "daysInYearPassedMonth", [[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365], [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]]);
  /**
   * The weekday, 1 = SUNDAY, 7 = SATURDAY. Access via
   * ICAL.Time.MONDAY, ICAL.Time.TUESDAY, ...
   *
   * @typedef {Number} weekDay
   * @memberof ICAL.Time
   */
  _defineProperty(Time, "SUNDAY", 1);
  _defineProperty(Time, "MONDAY", 2);
  _defineProperty(Time, "TUESDAY", 3);
  _defineProperty(Time, "WEDNESDAY", 4);
  _defineProperty(Time, "THURSDAY", 5);
  _defineProperty(Time, "FRIDAY", 6);
  _defineProperty(Time, "SATURDAY", 7);
  /**
   * The default weekday for the WKST part.
   * @constant
   * @default ICAL.Time.MONDAY
   */
  _defineProperty(Time, "DEFAULT_WEEK_START", 2);
  (function setupNormalizeAttributes() {
    // This needs to run before any instances are created!
    function defineAttr(attr) {
      Object.defineProperty(Time.prototype, attr, {
        get: function getTimeAttr() {
          if (this._pendingNormalization) {
            this._normalize();
            this._pendingNormalization = false;
          }
          return this._time[attr];
        },
        set: function setTimeAttr(val) {
          // Check if isDate will be set and if was not set to normalize date.
          // This avoids losing days when seconds, minutes and hours are zeroed
          // what normalize will do when time is a date.
          if (attr === "isDate" && val && !this._time.isDate) {
            this.adjust(0, 0, 0, 0);
          }
          this._cachedUnixTime = null;
          this._pendingNormalization = true;
          this._time[attr] = val;
        }
      });
    }
    defineAttr("year");
    defineAttr("month");
    defineAttr("day");
    defineAttr("hour");
    defineAttr("minute");
    defineAttr("second");
    defineAttr("isDate");
  })();

  var CHAR = /[^ \t]/;
  var VALUE_DELIMITER = ':';
  var PARAM_DELIMITER = ';';
  var PARAM_NAME_DELIMITER = '=';
  var DEFAULT_VALUE_TYPE$1 = 'unknown';
  var DEFAULT_PARAM_TYPE = 'text';
  var RFC6868_REPLACE_MAP$1 = {
    "^'": '"',
    "^n": "\n",
    "^^": "^"
  };

  /**
   * Parses iCalendar or vCard data into a raw jCal object. Consult
   * documentation on the {@tutorial layers|layers of parsing} for more
   * details.
   *
   * @function ICAL.parse
   * @memberof ICAL
   * @variation function
   * @todo Fix the API to be more clear on the return type
   * @param {String} input      The string data to parse
   * @return {Object|Object[]}  A single jCal object, or an array thereof
   */
  function parse(input) {
    var state = {};
    var root = state.component = [];
    state.stack = [root];
    parse._eachLine(input, function (err, line) {
      parse._handleContentLine(line, state);
    });

    // when there are still items on the stack
    // throw a fatal error, a component was not closed
    // correctly in that case.
    if (state.stack.length > 1) {
      throw new ParserError('invalid ical body. component began but did not end');
    }
    state = null;
    return root.length == 1 ? root[0] : root;
  }

  /**
   * Parse an iCalendar property value into the jCal for a single property
   *
   * @function ICAL.parse.property
   * @param {String} str
   *   The iCalendar property string to parse
   * @param {ICAL.design.designSet=} designSet
   *   The design data to use for this property
   * @return {Object}
   *   The jCal Object containing the property
   */
  parse.property = function (str, designSet) {
    var state = {
      component: [[], []],
      designSet: designSet || design.defaultSet
    };
    parse._handleContentLine(str, state);
    return state.component[1][0];
  };

  /**
   * Convenience method to parse a component. You can use ICAL.parse() directly
   * instead.
   *
   * @function ICAL.parse.component
   * @see ICAL.parse(function)
   * @param {String} str    The iCalendar component string to parse
   * @return {Object}       The jCal Object containing the component
   */
  parse.component = function (str) {
    return parse(str);
  };

  /**
   * An error that occurred during parsing.
   *
   * @param {String} message        The error message
   * @memberof ICAL.parse
   * @extends {Error}
   * @class
   */
  var ParserError = /*#__PURE__*/function (_Error) {
    function ParserError(message) {
      var _this;
      _classCallCheck(this, ParserError);
      _this = _callSuper(this, ParserError, [message]);
      _this.name = _this.constructor.name;
      try {
        throw new Error();
      } catch (e) {
        if (e.stack) {
          var split = e.stack.split('\n');
          split.shift();
          _this.stack = split.join('\n');
        }
      }
      return _this;
    }
    _inherits(ParserError, _Error);
    return _createClass(ParserError);
  }( /*#__PURE__*/_wrapNativeSuper(Error)); // classes & constants
  parse.ParserError = ParserError;

  /**
   * The state for parsing content lines from an iCalendar/vCard string.
   *
   * @private
   * @memberof ICAL.parse
   * @typedef {Object} parserState
   * @property {ICAL.design.designSet} designSet    The design set to use for parsing
   * @property {ICAL.Component[]} stack             The stack of components being processed
   * @property {ICAL.Component} component           The currently active component
   */

  /**
   * Handles a single line of iCalendar/vCard, updating the state.
   *
   * @private
   * @function ICAL.parse._handleContentLine
   * @param {String} line               The content line to process
   * @param {ICAL.parse.parserState}    The current state of the line parsing
   */
  parse._handleContentLine = function (line, state) {
    // break up the parts of the line
    var valuePos = line.indexOf(VALUE_DELIMITER);
    var paramPos = line.indexOf(PARAM_DELIMITER);
    var lastParamIndex;
    var lastValuePos;

    // name of property or begin/end
    var name;
    var value;
    // params is only overridden if paramPos !== -1.
    // we can't do params = params || {} later on
    // because it sacrifices ops.
    var params = {};

    /**
     * Different property cases
     *
     *
     * 1. RRULE:FREQ=foo
     *    // FREQ= is not a param but the value
     *
     * 2. ATTENDEE;ROLE=REQ-PARTICIPANT;
     *    // ROLE= is a param because : has not happened yet
     */
    // when the parameter delimiter is after the
    // value delimiter then it is not a parameter.

    if (paramPos !== -1 && valuePos !== -1) {
      // when the parameter delimiter is after the
      // value delimiter then it is not a parameter.
      if (paramPos > valuePos) {
        paramPos = -1;
      }
    }
    var parsedParams;
    if (paramPos !== -1) {
      name = line.slice(0, Math.max(0, paramPos)).toLowerCase();
      parsedParams = parse._parseParameters(line.slice(Math.max(0, paramPos)), 0, state.designSet);
      if (parsedParams[2] == -1) {
        throw new ParserError("Invalid parameters in '" + line + "'");
      }
      params = parsedParams[0];
      lastParamIndex = parsedParams[1].length + parsedParams[2] + paramPos;
      if ((lastValuePos = line.slice(Math.max(0, lastParamIndex)).indexOf(VALUE_DELIMITER)) !== -1) {
        value = line.slice(Math.max(0, lastParamIndex + lastValuePos + 1));
      } else {
        throw new ParserError("Missing parameter value in '" + line + "'");
      }
    } else if (valuePos !== -1) {
      // without parmeters (BEGIN:VCAENDAR, CLASS:PUBLIC)
      name = line.slice(0, Math.max(0, valuePos)).toLowerCase();
      value = line.slice(Math.max(0, valuePos + 1));
      if (name === 'begin') {
        var newComponent = [value.toLowerCase(), [], []];
        if (state.stack.length === 1) {
          state.component.push(newComponent);
        } else {
          state.component[2].push(newComponent);
        }
        state.stack.push(state.component);
        state.component = newComponent;
        if (!state.designSet) {
          state.designSet = design.getDesignSet(state.component[0]);
        }
        return;
      } else if (name === 'end') {
        state.component = state.stack.pop();
        return;
      }
      // If it is not begin/end, then this is a property with an empty value,
      // which should be considered valid.
    } else {
      /**
       * Invalid line.
       * The rational to throw an error is we will
       * never be certain that the rest of the file
       * is sane and it is unlikely that we can serialize
       * the result correctly either.
       */
      Logger.log(`ParserError(invalid line (no token ";" or ":") "${line}")`);
      return;
      }
    var valueType;
    var multiValue = false;
    var structuredValue = false;
    var propertyDetails;
    var splitName;
    var ungroupedName;

    // fetch the ungrouped part of the name
    if (state.designSet.propertyGroups && name.indexOf('.') !== -1) {
      splitName = name.split('.');
      params.group = splitName[0];
      ungroupedName = splitName[1];
    } else {
      ungroupedName = name;
    }
    if (ungroupedName in state.designSet.property) {
      propertyDetails = state.designSet.property[ungroupedName];
      if ('multiValue' in propertyDetails) {
        multiValue = propertyDetails.multiValue;
      }
      if ('structuredValue' in propertyDetails) {
        structuredValue = propertyDetails.structuredValue;
      }
      if (value && 'detectType' in propertyDetails) {
        valueType = propertyDetails.detectType(value);
      }
    }

    // attempt to determine value
    if (!valueType) {
      if (!('value' in params)) {
        if (propertyDetails) {
          valueType = propertyDetails.defaultType;
        } else {
          valueType = DEFAULT_VALUE_TYPE$1;
        }
      } else {
        // possible to avoid this?
        valueType = params.value.toLowerCase();
      }
    }
    delete params.value;

    /**
     * Note on `var result` juggling:
     *
     * I observed that building the array in pieces has adverse
     * effects on performance, so where possible we inline the creation.
     * It is a little ugly but resulted in ~2000 additional ops/sec.
     */

    var result;
    if (multiValue && structuredValue) {
      value = parse._parseMultiValue(value, structuredValue, valueType, [], multiValue, state.designSet, structuredValue);
      result = [ungroupedName, params, valueType, value];
    } else if (multiValue) {
      result = [ungroupedName, params, valueType];
      parse._parseMultiValue(value, multiValue, valueType, result, null, state.designSet, false);
    } else if (structuredValue) {
      value = parse._parseMultiValue(value, structuredValue, valueType, [], null, state.designSet, structuredValue);
      result = [ungroupedName, params, valueType, value];
    } else {
      value = parse._parseValue(value, valueType, state.designSet, false);
      result = [ungroupedName, params, valueType, value];
    }
    // rfc6350 requires that in vCard 4.0 the first component is the VERSION
    // component with as value 4.0, note that 3.0 does not have this requirement.
    if (state.component[0] === 'vcard' && state.component[1].length === 0 && !(name === 'version' && value === '4.0')) {
      state.designSet = design.getDesignSet("vcard3");
    }
    state.component[1].push(result);
  };

  /**
   * Parse a value from the raw value into the jCard/jCal value.
   *
   * @private
   * @function ICAL.parse._parseValue
   * @param {String} value          Original value
   * @param {String} type           Type of value
   * @param {Object} designSet      The design data to use for this value
   * @return {Object} varies on type
   */
  parse._parseValue = function (value, type, designSet, structuredValue) {
    if (type in designSet.value && 'fromICAL' in designSet.value[type]) {
      return designSet.value[type].fromICAL(value, structuredValue);
    }
    return value;
  };

  /**
   * Parse parameters from a string to object.
   *
   * @function ICAL.parse._parseParameters
   * @private
   * @param {String} line           A single unfolded line
   * @param {Numeric} start         Position to start looking for properties
   * @param {Object} designSet      The design data to use for this property
   * @return {Object} key/value pairs
   */
  parse._parseParameters = function (line, start, designSet) {
    var lastParam = start;
    var pos = 0;
    var delim = PARAM_NAME_DELIMITER;
    var result = {};
    var name, lcname;
    var value,
      valuePos = -1;
    var type, multiValue, mvdelim;

    // find the next '=' sign
    // use lastParam and pos to find name
    // check if " is used if so get value from "->"
    // then increment pos to find next ;

    while (pos !== false && (pos = unescapedIndexOf(line, delim, pos + 1)) !== -1) {
      name = line.slice(lastParam + 1, pos);
      if (name.length == 0) {
        throw new ParserError("Empty parameter name in '" + line + "'");
      }
      lcname = name.toLowerCase();
      mvdelim = false;
      multiValue = false;
      if (lcname in designSet.param && designSet.param[lcname].valueType) {
        type = designSet.param[lcname].valueType;
      } else {
        type = DEFAULT_PARAM_TYPE;
      }
      if (lcname in designSet.param) {
        multiValue = designSet.param[lcname].multiValue;
        if (designSet.param[lcname].multiValueSeparateDQuote) {
          mvdelim = parse._rfc6868Escape('"' + multiValue + '"');
        }
      }
      var nextChar = line[pos + 1];
      if (nextChar === '"') {
        valuePos = pos + 2;
        pos = unescapedIndexOf(line, '"', valuePos);
        if (multiValue && pos != -1) {
          var extendedValue = true;
          while (extendedValue) {
            if (line[pos + 1] == multiValue && line[pos + 2] == '"') {
              pos = unescapedIndexOf(line, '"', pos + 3);
            } else {
              extendedValue = false;
            }
          }
        }
        if (pos === -1) {
          throw new ParserError('invalid line (no matching double quote) "' + line + '"');
        }
        value = line.slice(valuePos, pos);
        lastParam = unescapedIndexOf(line, PARAM_DELIMITER, pos);
        if (lastParam === -1) {
          pos = false;
        }
      } else {
        valuePos = pos + 1;

        // move to next ";"
        var nextPos = unescapedIndexOf(line, PARAM_DELIMITER, valuePos);
        var propValuePos = unescapedIndexOf(line, VALUE_DELIMITER, valuePos);
        if (propValuePos !== -1 && nextPos > propValuePos) {
          // this is a delimiter in the property value, let's stop here
          nextPos = propValuePos;
          pos = false;
        } else if (nextPos === -1) {
          // no ";"
          if (propValuePos === -1) {
            nextPos = line.length;
          } else {
            nextPos = propValuePos;
          }
          pos = false;
        } else {
          lastParam = nextPos;
          pos = nextPos;
        }
        value = line.slice(valuePos, nextPos);
      }
      value = parse._rfc6868Escape(value);
      if (multiValue) {
        var delimiter = mvdelim || multiValue;
        value = parse._parseMultiValue(value, delimiter, type, [], null, designSet);
      } else {
        value = parse._parseValue(value, type, designSet);
      }
      if (multiValue && lcname in result) {
        if (Array.isArray(result[lcname])) {
          result[lcname].push(value);
        } else {
          result[lcname] = [result[lcname], value];
        }
      } else {
        result[lcname] = value;
      }
    }
    return [result, value, valuePos];
  };

  /**
   * Internal helper for rfc6868. Exposing this on ICAL.parse so that
   * hackers can disable the rfc6868 parsing if the really need to.
   *
   * @function ICAL.parse._rfc6868Escape
   * @param {String} val        The value to escape
   * @return {String}           The escaped value
   */
  parse._rfc6868Escape = function (val) {
    return val.replace(/\^['n^]/g, function (x) {
      return RFC6868_REPLACE_MAP$1[x];
    });
  };

  /**
   * Parse a multi value string. This function is used either for parsing
   * actual multi-value property's values, or for handling parameter values. It
   * can be used for both multi-value properties and structured value properties.
   *
   * @private
   * @function ICAL.parse._parseMultiValue
   * @param {String} buffer     The buffer containing the full value
   * @param {String} delim      The multi-value delimiter
   * @param {String} type       The value type to be parsed
   * @param {Array.<?>} result        The array to append results to, varies on value type
   * @param {String} innerMulti The inner delimiter to split each value with
   * @param {ICAL.design.designSet} designSet   The design data for this value
   * @return {?|Array.<?>}            Either an array of results, or the first result
   */
  parse._parseMultiValue = function (buffer, delim, type, result, innerMulti, designSet, structuredValue) {
    var pos = 0;
    var lastPos = 0;
    var value;
    if (delim.length === 0) {
      return buffer;
    }

    // split each piece
    while ((pos = unescapedIndexOf(buffer, delim, lastPos)) !== -1) {
      value = buffer.slice(lastPos, pos);
      if (innerMulti) {
        value = parse._parseMultiValue(value, innerMulti, type, [], null, designSet, structuredValue);
      } else {
        value = parse._parseValue(value, type, designSet, structuredValue);
      }
      result.push(value);
      lastPos = pos + delim.length;
    }

    // on the last piece take the rest of string
    value = buffer.slice(lastPos);
    if (innerMulti) {
      value = parse._parseMultiValue(value, innerMulti, type, [], null, designSet, structuredValue);
    } else {
      value = parse._parseValue(value, type, designSet, structuredValue);
    }
    result.push(value);
    return result.length == 1 ? result[0] : result;
  };

  /**
   * Process a complete buffer of iCalendar/vCard data line by line, correctly
   * unfolding content. Each line will be processed with the given callback
   *
   * @private
   * @function ICAL.parse._eachLine
   * @param {String} buffer                         The buffer to process
   * @param {function(?String, String)} callback    The callback for each line
   */
  parse._eachLine = function (buffer, callback) {
    var len = buffer.length;
    var lastPos = buffer.search(CHAR);
    var pos = lastPos;
    var line;
    var firstChar;
    var newlineOffset;
    do {
      pos = buffer.indexOf('\n', lastPos) + 1;
      if (pos > 1 && buffer[pos - 2] === '\r') {
        newlineOffset = 2;
      } else {
        newlineOffset = 1;
      }
      if (pos === 0) {
        pos = len;
        newlineOffset = 0;
      }
      firstChar = buffer[lastPos];
      if (firstChar === ' ' || firstChar === '\t') {
        // add to line
        line += buffer.slice(lastPos + 1, pos - newlineOffset);
      } else {
        if (line) callback(null, line);
        // push line
        line = buffer.slice(lastPos, pos - newlineOffset);
      }
      lastPos = pos;
    } while (pos !== len);

    // extra ending line
    line = line.trim();
    if (line.length) callback(null, line);
  };

  var OPTIONS = ["tzid", "location", "tznames", "latitude", "longitude"];

  /**
   * Timezone representation.
   *
   * @example
   * var vcalendar;
   * var timezoneComp = vcalendar.getFirstSubcomponent('vtimezone');
   * var tzid = timezoneComp.getFirstPropertyValue('tzid');
   *
   * var timezone = new ICAL.Timezone({
   *   component: timezoneComp,
   *   tzid
   * });
   *
   * @class
   * @alias ICAL.Timezone
   */
  var Timezone = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Timezone instance, by passing in a tzid and component.
     *
     * @param {ICAL.Component|Object} data options for class
     * @param {String|ICAL.Component} data.component
     *        If data is a simple object, then this member can be set to either a
     *        string containing the component data, or an already parsed
     *        ICAL.Component
     * @param {String} data.tzid      The timezone identifier
     * @param {String} data.location  The timezone locationw
     * @param {String} data.tznames   An alternative string representation of the
     *                                  timezone
     * @param {Number} data.latitude  The latitude of the timezone
     * @param {Number} data.longitude The longitude of the timezone
     */
    function Timezone(data) {
      _classCallCheck(this, Timezone);
      /**
       * Timezone identifier
       * @type {String}
       */
      _defineProperty(this, "tzid", "");
      /**
       * Timezone location
       * @type {String}
       */
      _defineProperty(this, "location", "");
      /**
       * Alternative timezone name, for the string representation
       * @type {String}
       */
      _defineProperty(this, "tznames", "");
      /**
       * The primary latitude for the timezone.
       * @type {Number}
       */
      _defineProperty(this, "latitude", 0.0);
      /**
       * The primary longitude for the timezone.
       * @type {Number}
       */
      _defineProperty(this, "longitude", 0.0);
      /**
       * The vtimezone component for this timezone.
       * @type {ICAL.Component}
       */
      _defineProperty(this, "component", null);
      /**
       * The year this timezone has been expanded to. All timezone transition
       * dates until this year are known and can be used for calculation
       *
       * @private
       * @type {Number}
       */
      _defineProperty(this, "expandedUntilYear", 0);
      /**
       * The class identifier.
       * @constant
       * @type {String}
       * @default "icaltimezone"
       */
      _defineProperty(this, "icalclass", "icaltimezone");
      this.wrappedJSObject = this;
      this.fromData(data);
    }
    return _createClass(Timezone, [{
      key: "fromData",
      value:
      /**
       * Sets up the current instance using members from the passed data object.
       *
       * @param {ICAL.Component|Object} aData options for class
       * @param {String|ICAL.Component} aData.component
       *        If aData is a simple object, then this member can be set to either a
       *        string containing the component data, or an already parsed
       *        ICAL.Component
       * @param {String} aData.tzid      The timezone identifier
       * @param {String} aData.location  The timezone locationw
       * @param {String} aData.tznames   An alternative string representation of the
       *                                  timezone
       * @param {Number} aData.latitude  The latitude of the timezone
       * @param {Number} aData.longitude The longitude of the timezone
       */
      function fromData(aData) {
        this.expandedUntilYear = 0;
        this.changes = [];
        if (aData instanceof Component) {
          // Either a component is passed directly
          this.component = aData;
        } else {
          // Otherwise the component may be in the data object
          if (aData && "component" in aData) {
            if (typeof aData.component == "string") {
              // If a string was passed, parse it as a component
              var jCal = parse(aData.component);
              this.component = new Component(jCal);
            } else if (aData.component instanceof Component) {
              // If it was a component already, then just set it
              this.component = aData.component;
            } else {
              // Otherwise just null out the component
              this.component = null;
            }
          }

          // Copy remaining passed properties
          for (var _i = 0, _OPTIONS = OPTIONS; _i < _OPTIONS.length; _i++) {
            var prop = _OPTIONS[_i];
            if (aData && prop in aData) {
              this[prop] = aData[prop];
            }
          }
        }

        // If we have a component but no TZID, attempt to get it from the
        // component's properties.
        if (this.component instanceof Component && !this.tzid) {
          this.tzid = this.component.getFirstPropertyValue('tzid');
        }
        return this;
      }

      /**
       * Finds the utcOffset the given time would occur in this timezone.
       *
       * @param {ICAL.Time} tt        The time to check for
       * @return {Number} utc offset in seconds
       */
    }, {
      key: "utcOffset",
      value: function utcOffset(tt) {
        if (this == Timezone.utcTimezone || this == Timezone.localTimezone) {
          return 0;
        }
        this._ensureCoverage(tt.year);
        if (!this.changes.length) {
          return 0;
        }
        var tt_change = {
          year: tt.year,
          month: tt.month,
          day: tt.day,
          hour: tt.hour,
          minute: tt.minute,
          second: tt.second
        };
        var change_num = this._findNearbyChange(tt_change);
        var change_num_to_use = -1;
        var step = 1;

        // TODO: replace with bin search?
        for (;;) {
          var change = clone(this.changes[change_num], true);
          if (change.utcOffset < change.prevUtcOffset) {
            Timezone.adjust_change(change, 0, 0, 0, change.utcOffset);
          } else {
            Timezone.adjust_change(change, 0, 0, 0, change.prevUtcOffset);
          }
          var cmp = Timezone._compare_change_fn(tt_change, change);
          if (cmp >= 0) {
            change_num_to_use = change_num;
          } else {
            step = -1;
          }
          if (step == -1 && change_num_to_use != -1) {
            break;
          }
          change_num += step;
          if (change_num < 0) {
            return 0;
          }
          if (change_num >= this.changes.length) {
            break;
          }
        }
        var zone_change = this.changes[change_num_to_use];
        var utcOffset_change = zone_change.utcOffset - zone_change.prevUtcOffset;
        if (utcOffset_change < 0 && change_num_to_use > 0) {
          var tmp_change = clone(zone_change, true);
          Timezone.adjust_change(tmp_change, 0, 0, 0, tmp_change.prevUtcOffset);
          if (Timezone._compare_change_fn(tt_change, tmp_change) < 0) {
            var prev_zone_change = this.changes[change_num_to_use - 1];
            var want_daylight = false; // TODO

            if (zone_change.is_daylight != want_daylight && prev_zone_change.is_daylight == want_daylight) {
              zone_change = prev_zone_change;
            }
          }
        }

        // TODO return is_daylight?
        return zone_change.utcOffset;
      }
    }, {
      key: "_findNearbyChange",
      value: function _findNearbyChange(change) {
        // find the closest match
        var idx = binsearchInsert(this.changes, change, Timezone._compare_change_fn);
        if (idx >= this.changes.length) {
          return this.changes.length - 1;
        }
        return idx;
      }
    }, {
      key: "_ensureCoverage",
      value: function _ensureCoverage(aYear) {
        if (Timezone._minimumExpansionYear == -1) {
          var today = Time.now();
          Timezone._minimumExpansionYear = today.year;
        }
        var changesEndYear = aYear;
        if (changesEndYear < Timezone._minimumExpansionYear) {
          changesEndYear = Timezone._minimumExpansionYear;
        }
        changesEndYear += Timezone.EXTRA_COVERAGE;
        if (!this.changes.length || this.expandedUntilYear < aYear) {
          var subcomps = this.component.getAllSubcomponents();
          var compLen = subcomps.length;
          var compIdx = 0;
          for (; compIdx < compLen; compIdx++) {
            this._expandComponent(subcomps[compIdx], changesEndYear, this.changes);
          }
          this.changes.sort(Timezone._compare_change_fn);
          this.expandedUntilYear = changesEndYear;
        }
      }
    }, {
      key: "_expandComponent",
      value: function _expandComponent(aComponent, aYear, changes) {
        if (!aComponent.hasProperty("dtstart") || !aComponent.hasProperty("tzoffsetto") || !aComponent.hasProperty("tzoffsetfrom")) {
          return null;
        }
        var dtstart = aComponent.getFirstProperty("dtstart").getFirstValue();
        var change;
        function convert_tzoffset(offset) {
          return offset.factor * (offset.hours * 3600 + offset.minutes * 60);
        }
        function init_changes() {
          var changebase = {};
          changebase.is_daylight = aComponent.name == "daylight";
          changebase.utcOffset = convert_tzoffset(aComponent.getFirstProperty("tzoffsetto").getFirstValue());
          changebase.prevUtcOffset = convert_tzoffset(aComponent.getFirstProperty("tzoffsetfrom").getFirstValue());
          return changebase;
        }
        if (!aComponent.hasProperty("rrule") && !aComponent.hasProperty("rdate")) {
          change = init_changes();
          change.year = dtstart.year;
          change.month = dtstart.month;
          change.day = dtstart.day;
          change.hour = dtstart.hour;
          change.minute = dtstart.minute;
          change.second = dtstart.second;
          Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset);
          changes.push(change);
        } else {
          var props = aComponent.getAllProperties("rdate");
          var _iterator = _createForOfIteratorHelper(props),
            _step;
          try {
            for (_iterator.s(); !(_step = _iterator.n()).done;) {
              var rdate = _step.value;
              var time = rdate.getFirstValue();
              change = init_changes();
              change.year = time.year;
              change.month = time.month;
              change.day = time.day;
              if (time.isDate) {
                change.hour = dtstart.hour;
                change.minute = dtstart.minute;
                change.second = dtstart.second;
                if (dtstart.zone != Timezone.utcTimezone) {
                  Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset);
                }
              } else {
                change.hour = time.hour;
                change.minute = time.minute;
                change.second = time.second;
                if (time.zone != Timezone.utcTimezone) {
                  Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset);
                }
              }
              changes.push(change);
            }
          } catch (err) {
            _iterator.e(err);
          } finally {
            _iterator.f();
          }
          var rrule = aComponent.getFirstProperty("rrule");
          if (rrule) {
            rrule = rrule.getFirstValue();
            change = init_changes();
            if (rrule.until && rrule.until.zone == Timezone.utcTimezone) {
              rrule.until.adjust(0, 0, 0, change.prevUtcOffset);
              rrule.until.zone = Timezone.localTimezone;
            }
            var iterator = rrule.iterator(dtstart);
            var occ;
            while (occ = iterator.next()) {
              change = init_changes();
              if (occ.year > aYear || !occ) {
                break;
              }
              change.year = occ.year;
              change.month = occ.month;
              change.day = occ.day;
              change.hour = occ.hour;
              change.minute = occ.minute;
              change.second = occ.second;
              change.isDate = occ.isDate;
              Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset);
              changes.push(change);
            }
          }
        }
        return changes;
      }

      /**
       * The string representation of this timezone.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        return this.tznames ? this.tznames : this.tzid;
      }
    }], [{
      key: "_compare_change_fn",
      value: function _compare_change_fn(a, b) {
        if (a.year < b.year) return -1;else if (a.year > b.year) return 1;
        if (a.month < b.month) return -1;else if (a.month > b.month) return 1;
        if (a.day < b.day) return -1;else if (a.day > b.day) return 1;
        if (a.hour < b.hour) return -1;else if (a.hour > b.hour) return 1;
        if (a.minute < b.minute) return -1;else if (a.minute > b.minute) return 1;
        if (a.second < b.second) return -1;else if (a.second > b.second) return 1;
        return 0;
      }

      /**
       * Convert the date/time from one zone to the next.
       *
       * @param {ICAL.Time} tt                  The time to convert
       * @param {ICAL.Timezone} from_zone       The source zone to convert from
       * @param {ICAL.Timezone} to_zone         The target zone to convert to
       * @return {ICAL.Time}                    The converted date/time object
       */
    }, {
      key: "convert_time",
      value: function convert_time(tt, from_zone, to_zone) {
        if (tt.isDate || from_zone.tzid == to_zone.tzid || from_zone == Timezone.localTimezone || to_zone == Timezone.localTimezone) {
          tt.zone = to_zone;
          return tt;
        }
        var utcOffset = from_zone.utcOffset(tt);
        tt.adjust(0, 0, 0, -utcOffset);
        utcOffset = to_zone.utcOffset(tt);
        tt.adjust(0, 0, 0, utcOffset);
        return null;
      }

      /**
       * Creates a new ICAL.Timezone instance from the passed data object.
       *
       * @param {ICAL.Component|Object} aData options for class
       * @param {String|ICAL.Component} aData.component
       *        If aData is a simple object, then this member can be set to either a
       *        string containing the component data, or an already parsed
       *        ICAL.Component
       * @param {String} aData.tzid      The timezone identifier
       * @param {String} aData.location  The timezone locationw
       * @param {String} aData.tznames   An alternative string representation of the
       *                                  timezone
       * @param {Number} aData.latitude  The latitude of the timezone
       * @param {Number} aData.longitude The longitude of the timezone
       */
    }, {
      key: "fromData",
      value: function fromData(aData) {
        var tt = new Timezone();
        return tt.fromData(aData);
      }

      /**
       * The instance describing the UTC timezone
       * @type {ICAL.Timezone}
       * @constant
       * @instance
       */
    }, {
      key: "utcTimezone",
      get: function get() {
        if (!_assertClassBrand(Timezone, this, _utcTimezone)._) {
          _utcTimezone._ = _assertClassBrand(Timezone, this, Timezone.fromData({
            tzid: "UTC"
          }));
        }
        return _assertClassBrand(Timezone, this, _utcTimezone)._;
      }

      /**
       * The instance describing the local timezone
       * @type {ICAL.Timezone}
       * @constant
       * @instance
       */
    }, {
      key: "localTimezone",
      get: function get() {
        if (!_assertClassBrand(Timezone, this, _localTimezone)._) {
          _localTimezone._ = _assertClassBrand(Timezone, this, Timezone.fromData({
            tzid: "floating"
          }));
        }
        return _assertClassBrand(Timezone, this, _localTimezone)._;
      }

      /**
       * Adjust a timezone change object.
       * @private
       * @param {Object} change     The timezone change object
       * @param {Number} days       The extra amount of days
       * @param {Number} hours      The extra amount of hours
       * @param {Number} minutes    The extra amount of minutes
       * @param {Number} seconds    The extra amount of seconds
       */
    }, {
      key: "adjust_change",
      value: function adjust_change(change, days, hours, minutes, seconds) {
        return Time.prototype.adjust.call(change, days, hours, minutes, seconds, change);
      }
    }]);
  }();
  var _utcTimezone = {
    _: null
  };
  var _localTimezone = {
    _: null
  };
  _defineProperty(Timezone, "_minimumExpansionYear", -1);
  _defineProperty(Timezone, "EXTRA_COVERAGE", 5);

  /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   * Portions Copyright (C) Philipp Kewisch */
  var zones = null;

  /**
   * @classdesc
   * Singleton class to contain timezones.  Right now it is all manual registry in
   * the future we may use this class to download timezone information or handle
   * loading pre-expanded timezones.
   *
   * @exports module:ICAL.TimezoneService
   * @alias ICAL.TimezoneService
   */
  var TimezoneService = {
    get count() {
      if (zones === null) {
        return 0;
      }
      return Object.keys(zones).length;
    },
    reset: function reset() {
      zones = Object.create(null);
      var utc = Timezone.utcTimezone;
      zones.Z = utc;
      zones.UTC = utc;
      zones.GMT = utc;
    },
    /**
     * Checks if timezone id has been registered.
     *
     * @param {String} tzid     Timezone identifier (e.g. America/Los_Angeles)
     * @return {Boolean}        False, when not present
     */
    has: function has(tzid) {
      if (zones === null) {
        return false;
      }
      return !!zones[tzid];
    },
    /**
     * Returns a timezone by its tzid if present.
     *
     * @param {String} tzid     Timezone identifier (e.g. America/Los_Angeles)
     * @return {?ICAL.Timezone} The timezone, or null if not found
     */
    get: function get(tzid) {
      if (zones === null) {
        this.reset();
      }
      return zones[tzid];
    },
    /**
     * Registers a timezone object or component.
     *
     * @param {String=} name
     *        The name of the timezone. Defaults to the component's TZID if not
     *        passed.
     * @param {ICAL.Component|ICAL.Timezone} zone
     *        The initialized zone or vtimezone.
     */
    register: function register(name, timezone) {
      if (zones === null) {
        this.reset();
      }
      if (name instanceof Component) {
        if (name.name === 'vtimezone') {
          timezone = new Timezone(name);
          name = timezone.tzid;
        }
      }
      if (timezone instanceof Timezone) {
        zones[name] = timezone;
      } else {
        throw new TypeError('timezone must be ICAL.Timezone or ICAL.Component');
      }
    },
    /**
     * Removes a timezone by its tzid from the list.
     *
     * @param {String} tzid     Timezone identifier (e.g. America/Los_Angeles)
     * @return {?ICAL.Timezone} The removed timezone, or null if not registered
     */
    remove: function remove(tzid) {
      if (zones === null) {
        return null;
      }
      return delete zones[tzid];
    }
  };

  /**
   * Helper functions used in various places within ical.js
   * @module ICAL.helpers
   */

  /**
   * Compiles a list of all referenced TZIDs in all subcomponents and
   * removes any extra VTIMEZONE subcomponents. In addition, if any TZIDs
   * are referenced by a component, but a VTIMEZONE does not exist,
   * an attempt will be made to generate a VTIMEZONE using ICAL.TimezoneService.
   *
   * @param {ICAL.Component} vcal     The top-level VCALENDAR component.
   * @return {ICAL.Component}         The ICAL.Component that was passed in.
   */
  function updateTimezones(vcal) {
    var allsubs, properties, vtimezones, reqTzid, i;
    if (!vcal || vcal.name !== "vcalendar") {
      //not a top-level vcalendar component
      return vcal;
    }

    //Store vtimezone subcomponents in an object reference by tzid.
    //Store properties from everything else in another array
    allsubs = vcal.getAllSubcomponents();
    properties = [];
    vtimezones = {};
    for (i = 0; i < allsubs.length; i++) {
      if (allsubs[i].name === "vtimezone") {
        var tzid = allsubs[i].getFirstProperty("tzid").getFirstValue();
        vtimezones[tzid] = allsubs[i];
      } else {
        properties = properties.concat(allsubs[i].getAllProperties());
      }
    }

    //create an object with one entry for each required tz
    reqTzid = {};
    for (i = 0; i < properties.length; i++) {
      var _tzid = properties[i].getParameter("tzid");
      if (_tzid) {
        reqTzid[_tzid] = true;
      }
    }

    //delete any vtimezones that are not on the reqTzid list.
    for (var _i = 0, _Object$entries = Object.entries(vtimezones); _i < _Object$entries.length; _i++) {
      var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
        _tzid2 = _Object$entries$_i[0],
        comp = _Object$entries$_i[1];
      if (!reqTzid[_tzid2]) {
        vcal.removeSubcomponent(comp);
      }
    }

    //create any missing, but registered timezones
    for (var _i2 = 0, _Object$keys = Object.keys(reqTzid); _i2 < _Object$keys.length; _i2++) {
      var _tzid3 = _Object$keys[_i2];
      if (!vtimezones[_tzid3] && TimezoneService.has(_tzid3) && TimezoneService.get(_tzid3).component) {
        vcal.addSubcomponent(TimezoneService.get(_tzid3).component);
      }
    }
    return vcal;
  }

  /**
   * Checks if the given type is of the number type and also NaN.
   *
   * @param {Number} number     The number to check
   * @return {Boolean}          True, if the number is strictly NaN
   */
  function isStrictlyNaN(number) {
    return typeof number === 'number' && isNaN(number);
  }

  /**
   * Parses a string value that is expected to be an integer, when the valid is
   * not an integer throws a decoration error.
   *
   * @param {String} string     Raw string input
   * @return {Number}           Parsed integer
   */
  function strictParseInt(string) {
    var result = parseInt(string, 10);
    if (isStrictlyNaN(result)) {
      throw new Error('Could not extract integer from "' + string + '"');
    }
    return result;
  }

  /**
   * Creates or returns a class instance of a given type with the initialization
   * data if the data is not already an instance of the given type.
   *
   * @example
   * var time = new ICAL.Time(...);
   * var result = ICAL.helpers.formatClassType(time, ICAL.Time);
   *
   * (result instanceof ICAL.Time)
   * // => true
   *
   * result = ICAL.helpers.formatClassType({}, ICAL.Time);
   * (result isntanceof ICAL.Time)
   * // => true
   *
   *
   * @param {Object} data       object initialization data
   * @param {Object} type       object type (like ICAL.Time)
   * @return {?}                An instance of the found type.
   */
  function formatClassType(data, type) {
    if (typeof data === 'undefined') {
      return undefined;
    }
    if (data instanceof type) {
      return data;
    }
    return new type(data);
  }

  /**
   * Identical to indexOf but will only match values when they are not preceded
   * by a backslash character.
   *
   * @param {String} buffer         String to search
   * @param {String} search         Value to look for
   * @param {Number} pos            Start position
   * @return {Number}               The position, or -1 if not found
   */
  function unescapedIndexOf(buffer, search, pos) {
    while ((pos = buffer.indexOf(search, pos)) !== -1) {
      if (pos > 0 && buffer[pos - 1] === '\\') {
        pos += 1;
      } else {
        return pos;
      }
    }
    return -1;
  }

  /**
   * Find the index for insertion using binary search.
   *
   * @param {Array} list            The list to search
   * @param {?} seekVal             The value to insert
   * @param {function(?,?)} cmpfunc The comparison func, that can
   *                                  compare two seekVals
   * @return {Number}               The insert position
   */
  function binsearchInsert(list, seekVal, cmpfunc) {
    if (!list.length) return 0;
    var low = 0,
      high = list.length - 1,
      mid,
      cmpval;
    while (low <= high) {
      mid = low + Math.floor((high - low) / 2);
      cmpval = cmpfunc(seekVal, list[mid]);
      if (cmpval < 0) high = mid - 1;else if (cmpval > 0) low = mid + 1;else break;
    }
    if (cmpval < 0) return mid; // insertion is displacing, so use mid outright.
    else if (cmpval > 0) return mid + 1;else return mid;
  }

  /**
   * Clone the passed object or primitive. By default a shallow clone will be
   * executed.
   *
   * @param {*} aSrc            The thing to clone
   * @param {Boolean=} aDeep    If true, a deep clone will be performed
   * @return {*}                The copy of the thing
   */
  function clone(aSrc, aDeep) {
    if (!aSrc || _typeof(aSrc) != "object") {
      return aSrc;
    } else if (aSrc instanceof Date) {
      return new Date(aSrc.getTime());
    } else if ("clone" in aSrc) {
      return aSrc.clone();
    } else if (Array.isArray(aSrc)) {
      var arr = [];
      for (var i = 0; i < aSrc.length; i++) {
        arr.push(aDeep ? clone(aSrc[i], true) : aSrc[i]);
      }
      return arr;
    } else {
      var obj = {};
      for (var _i3 = 0, _Object$entries2 = Object.entries(aSrc); _i3 < _Object$entries2.length; _i3++) {
        var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i3], 2),
          name = _Object$entries2$_i[0],
          value = _Object$entries2$_i[1];
        if (aDeep) {
          obj[name] = clone(value, true);
        } else {
          obj[name] = value;
        }
      }
      return obj;
    }
  }

  /**
   * Performs iCalendar line folding. A line ending character is inserted and
   * the next line begins with a whitespace.
   *
   * @example
   * SUMMARY:This line will be fold
   *  ed right in the middle of a word.
   *
   * @param {String} aLine      The line to fold
   * @return {String}           The folded line
   */
  function foldline(aLine) {
    var result = "";
    var line = aLine || "",
      pos = 0,
      line_length = 0;
    //pos counts position in line for the UTF-16 presentation
    //line_length counts the bytes for the UTF-8 presentation
    while (line.length) {
      var cp = line.codePointAt(pos);
      if (cp < 128) ++line_length;else if (cp < 2048) line_length += 2; //needs 2 UTF-8 bytes
      else if (cp < 65536) line_length += 3;else line_length += 4; //cp is less than 1114112
      if (line_length < ICALmodule.foldLength + 1) pos += cp > 65535 ? 2 : 1;else {
        result += ICALmodule.newLineChar + " " + line.slice(0, Math.max(0, pos));
        line = line.slice(Math.max(0, pos));
        pos = line_length = 0;
      }
    }
    return result.slice(ICALmodule.newLineChar.length + 1);
  }

  /**
   * Pads the given string or number with zeros so it will have at least two
   * characters.
   *
   * @param {String|Number} data    The string or number to pad
   * @return {String}               The number padded as a string
   */
  function pad2(data) {
    if (typeof data !== 'string') {
      // handle fractions.
      if (typeof data === 'number') {
        data = parseInt(data);
      }
      data = String(data);
    }
    var len = data.length;
    switch (len) {
      case 0:
        return '00';
      case 1:
        return '0' + data;
      default:
        return data;
    }
  }

  /**
   * Truncates the given number, correctly handling negative numbers.
   *
   * @param {Number} number     The number to truncate
   * @return {Number}           The truncated number
   */
  function trunc(number) {
    return number < 0 ? Math.ceil(number) : Math.floor(number);
  }

  /**
   * Poor-man's cross-browser object extension. Doesn't support all the
   * features, but enough for our usage. Note that the target's properties are
   * not overwritten with the source properties.
   *
   * @example
   * var child = ICAL.helpers.extend(parent, {
   *   "bar": 123
   * });
   *
   * @param {Object} source     The object to extend
   * @param {Object} target     The object to extend with
   * @return {Object}           Returns the target.
   */
  function extend(source, target) {
    for (var key in source) {
      var descr = Object.getOwnPropertyDescriptor(source, key);
      if (descr && !Object.getOwnPropertyDescriptor(target, key)) {
        Object.defineProperty(target, key, descr);
      }
    }
    return target;
  }

  var helpers = /*#__PURE__*/Object.freeze({
    __proto__: null,
    updateTimezones: updateTimezones,
    isStrictlyNaN: isStrictlyNaN,
    strictParseInt: strictParseInt,
    formatClassType: formatClassType,
    unescapedIndexOf: unescapedIndexOf,
    binsearchInsert: binsearchInsert,
    clone: clone,
    foldline: foldline,
    pad2: pad2,
    trunc: trunc,
    extend: extend
  });

  /**
   * This class represents the "utc-offset" value type, with various calculation and manipulation
   * methods.
   *
   * @class
   * @alias ICAL.UtcOffset
   */
  var UtcOffset = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.UtcOffset instance.
     *
     * @param {Object} aData          An object with members of the utc offset
     * @param {Number=} aData.hours   The hours for the utc offset
     * @param {Number=} aData.minutes The minutes in the utc offset
     * @param {Number=} aData.factor  The factor for the utc-offset, either -1 or 1
     */
    function UtcOffset(aData) {
      _classCallCheck(this, UtcOffset);
      /**
       * The hours in the utc-offset
       * @type {Number}
       */
      _defineProperty(this, "hours", 0);
      /**
       * The minutes in the utc-offset
       * @type {Number}
       */
      _defineProperty(this, "minutes", 0);
      /**
       * The sign of the utc offset, 1 for positive offset, -1 for negative
       * offsets.
       * @type {Number}
       */
      _defineProperty(this, "factor", 1);
      /**
       * The type name, to be used in the jCal object.
       * @constant
       * @type {String}
       * @default "utc-offset"
       */
      _defineProperty(this, "icaltype", "utc-offset");
      this.fromData(aData);
    }
    return _createClass(UtcOffset, [{
      key: "clone",
      value:
      /**
       * Returns a clone of the utc offset object.
       *
       * @return {ICAL.UtcOffset}     The cloned object
       */
      function clone() {
        return UtcOffset.fromSeconds(this.toSeconds());
      }

      /**
       * Sets up the current instance using members from the passed data object.
       *
       * @param {Object} aData          An object with members of the utc offset
       * @param {Number=} aData.hours   The hours for the utc offset
       * @param {Number=} aData.minutes The minutes in the utc offset
       * @param {Number=} aData.factor  The factor for the utc-offset, either -1 or 1
       */
    }, {
      key: "fromData",
      value: function fromData(aData) {
        if (aData) {
          for (var _i = 0, _Object$entries = Object.entries(aData); _i < _Object$entries.length; _i++) {
            var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
              key = _Object$entries$_i[0],
              value = _Object$entries$_i[1];
            this[key] = value;
          }
        }
        this._normalize();
      }

      /**
       * Sets up the current instance from the given seconds value. The seconds
       * value is truncated to the minute. Offsets are wrapped when the world
       * ends, the hour after UTC+14:00 is UTC-12:00.
       *
       * @param {Number} aSeconds         The seconds to convert into an offset
       */
    }, {
      key: "fromSeconds",
      value: function fromSeconds(aSeconds) {
        var secs = Math.abs(aSeconds);
        this.factor = aSeconds < 0 ? -1 : 1;
        this.hours = trunc(secs / 3600);
        secs -= this.hours * 3600;
        this.minutes = trunc(secs / 60);
        return this;
      }

      /**
       * Convert the current offset to a value in seconds
       *
       * @return {Number}                 The offset in seconds
       */
    }, {
      key: "toSeconds",
      value: function toSeconds() {
        return this.factor * (60 * this.minutes + 3600 * this.hours);
      }

      /**
       * Compare this utc offset with another one.
       *
       * @param {ICAL.UtcOffset} other        The other offset to compare with
       * @return {Number}                     -1, 0 or 1 for less/equal/greater
       */
    }, {
      key: "compare",
      value: function compare(other) {
        var a = this.toSeconds();
        var b = other.toSeconds();
        return (a > b) - (b > a);
      }
    }, {
      key: "_normalize",
      value: function _normalize() {
        // Range: 97200 seconds (with 1 hour inbetween)
        var secs = this.toSeconds();
        var factor = this.factor;
        while (secs < -43200) {
          // = UTC-12:00
          secs += 97200;
        }
        while (secs > 50400) {
          // = UTC+14:00
          secs -= 97200;
        }
        this.fromSeconds(secs);

        // Avoid changing the factor when on zero seconds
        if (secs == 0) {
          this.factor = factor;
        }
      }

      /**
       * The iCalendar string representation of this utc-offset.
       * @return {String}
       */
    }, {
      key: "toICALString",
      value: function toICALString() {
        return design.icalendar.value['utc-offset'].toICAL(this.toString());
      }

      /**
       * The string representation of this utc-offset.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        return (this.factor == 1 ? "+" : "-") + pad2(this.hours) + ':' + pad2(this.minutes);
      }
    }], [{
      key: "fromString",
      value:
      /**
       * Creates a new {@link ICAL.UtcOffset} instance from the passed string.
       *
       * @param {String} aString    The string to parse
       * @return {ICAL.Duration}    The created utc-offset instance
       */
      function fromString(aString) {
        // -05:00
        var options = {};
        //TODO: support seconds per rfc5545 ?
        options.factor = aString[0] === '+' ? 1 : -1;
        options.hours = strictParseInt(aString.slice(1, 3));
        options.minutes = strictParseInt(aString.slice(4, 6));
        return new UtcOffset(options);
      }

      /**
       * Creates a new {@link ICAL.UtcOffset} instance from the passed seconds
       * value.
       *
       * @param {Number} aSeconds       The number of seconds to convert
       */
    }, {
      key: "fromSeconds",
      value: function fromSeconds(aSeconds) {
        var instance = new UtcOffset();
        instance.fromSeconds(aSeconds);
        return instance;
      }
    }]);
  }();

  /**
   * Describes a vCard time, which has slight differences to the ICAL.Time.
   * Properties can be null if not specified, for example for dates with
   * reduced accuracy or truncation.
   *
   * Note that currently not all methods are correctly re-implemented for
   * VCardTime. For example, comparison will have undefined results when some
   * members are null.
   *
   * Also, normalization is not yet implemented for this class!
   *
   * @alias ICAL.VCardTime
   * @extends {ICAL.Time}
   * @class
   */
  var VCardTime = /*#__PURE__*/function (_Time) {
    /**
     * Creates a new ICAL.VCardTime instance.
     *
     * @param {Object} data                           The data for the time instance
     * @param {Number=} data.year                     The year for this date
     * @param {Number=} data.month                    The month for this date
     * @param {Number=} data.day                      The day for this date
     * @param {Number=} data.hour                     The hour for this date
     * @param {Number=} data.minute                   The minute for this date
     * @param {Number=} data.second                   The second for this date
     * @param {ICAL.Timezone|ICAL.UtcOffset} zone     The timezone to use
     * @param {String} icaltype                       The type for this date/time object
     */
    function VCardTime(data, zone, icaltype) {
      var _this;
      _classCallCheck(this, VCardTime);
      _this = _callSuper(this, VCardTime, [data, zone]);
      /**
       * The class identifier.
       * @constant
       * @type {String}
       * @default "vcardtime"
       */
      _defineProperty(_this, "icalclass", "vcardtime");
      /**
       * The type name, to be used in the jCal object.
       * @type {String}
       * @default "date-and-or-time"
       */
      _defineProperty(_this, "icaltype", "date-and-or-time");
      _this.icaltype = icaltype || "date-and-or-time";
      return _this;
    }
    _inherits(VCardTime, _Time);
    return _createClass(VCardTime, [{
      key: "clone",
      value:
      /**
       * Returns a clone of the vcard date/time object.
       *
       * @return {ICAL.VCardTime}     The cloned object
       */
      function clone() {
        return new VCardTime(this._time, this.zone, this.icaltype);
      }
    }, {
      key: "_normalize",
      value: function _normalize() {
        return this;
      }

      /**
       * @inheritdoc
       */
    }, {
      key: "utcOffset",
      value: function utcOffset() {
        if (this.zone instanceof UtcOffset) {
          return this.zone.toSeconds();
        } else {
          return Time.prototype.utcOffset.apply(this, arguments);
        }
      }

      /**
       * Returns an RFC 6350 compliant representation of this object.
       *
       * @return {String}         vcard date/time string
       */
    }, {
      key: "toICALString",
      value: function toICALString() {
        return design.vcard.value[this.icaltype].toICAL(this.toString());
      }

      /**
       * The string representation of this date/time, in jCard form
       * (including : and - separators).
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        var y = this.year,
          m = this.month,
          d = this.day;
        var h = this.hour,
          mm = this.minute,
          s = this.second;
        var hasYear = y !== null,
          hasMonth = m !== null,
          hasDay = d !== null;
        var hasHour = h !== null,
          hasMinute = mm !== null,
          hasSecond = s !== null;
        var datepart = (hasYear ? pad2(y) + (hasMonth || hasDay ? '-' : '') : hasMonth || hasDay ? '--' : '') + (hasMonth ? pad2(m) : '') + (hasDay ? '-' + pad2(d) : '');
        var timepart = (hasHour ? pad2(h) : '-') + (hasHour && hasMinute ? ':' : '') + (hasMinute ? pad2(mm) : '') + (!hasHour && !hasMinute ? '-' : '') + (hasMinute && hasSecond ? ':' : '') + (hasSecond ? pad2(s) : '');
        var zone;
        if (this.zone === Timezone.utcTimezone) {
          zone = 'Z';
        } else if (this.zone instanceof UtcOffset) {
          zone = this.zone.toString();
        } else if (this.zone === Timezone.localTimezone) {
          zone = '';
        } else if (this.zone instanceof Timezone) {
          var offset = UtcOffset.fromSeconds(this.zone.utcOffset(this));
          zone = offset.toString();
        } else {
          zone = '';
        }
        switch (this.icaltype) {
          case "time":
            return timepart + zone;
          case "date-and-or-time":
          case "date-time":
            return datepart + (timepart == '--' ? '' : 'T' + timepart + zone);
          case "date":
            return datepart;
        }
        return null;
      }
    }], [{
      key: "fromDateAndOrTimeString",
      value:
      /**
       * Returns a new ICAL.VCardTime instance from a date and/or time string.
       *
       * @param {String} aValue     The string to create from
       * @param {String} aIcalType  The type for this instance, e.g. date-and-or-time
       * @return {ICAL.VCardTime}   The date/time instance
       */
      function fromDateAndOrTimeString(aValue, aIcalType) {
        function part(v, s, e) {
          return v ? strictParseInt(v.slice(s, s + e)) : null;
        }
        var parts = aValue.split('T');
        var dt = parts[0],
          tmz = parts[1];
        var splitzone = tmz ? design.vcard.value.time._splitZone(tmz) : [];
        var zone = splitzone[0],
          tm = splitzone[1];
        var dtlen = dt ? dt.length : 0;
        var tmlen = tm ? tm.length : 0;
        var hasDashDate = dt && dt[0] == '-' && dt[1] == '-';
        var hasDashTime = tm && tm[0] == '-';
        var o = {
          year: hasDashDate ? null : part(dt, 0, 4),
          month: hasDashDate && (dtlen == 4 || dtlen == 7) ? part(dt, 2, 2) : dtlen == 7 ? part(dt, 5, 2) : dtlen == 10 ? part(dt, 5, 2) : null,
          day: dtlen == 5 ? part(dt, 3, 2) : dtlen == 7 && hasDashDate ? part(dt, 5, 2) : dtlen == 10 ? part(dt, 8, 2) : null,
          hour: hasDashTime ? null : part(tm, 0, 2),
          minute: hasDashTime && tmlen == 3 ? part(tm, 1, 2) : tmlen > 4 ? hasDashTime ? part(tm, 1, 2) : part(tm, 3, 2) : null,
          second: tmlen == 4 ? part(tm, 2, 2) : tmlen == 6 ? part(tm, 4, 2) : tmlen == 8 ? part(tm, 6, 2) : null
        };
        if (zone == 'Z') {
          zone = Timezone.utcTimezone;
        } else if (zone && zone[3] == ':') {
          zone = UtcOffset.fromString(zone);
        } else {
          zone = null;
        }
        return new VCardTime(o, zone, aIcalType);
      }
    }]);
  }(Time);

  /**
   * An iterator for a single recurrence rule. This class usually doesn't have to be instanciated
   * directly, the convenience method {@link ICAL.Recur#iterator} can be used.
   *
   * @class
   * @alias ICAL.RecurIterator
   */
  var RecurIterator = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.RecurIterator instance. The options object may contain additional members
     * when resuming iteration from a previous run.
     *
     * @param {Object} options                The iterator options
     * @param {ICAL.Recur} options.rule       The rule to iterate.
     * @param {ICAL.Time} options.dtstart     The start date of the event.
     * @param {Boolean=} options.initialized  When true, assume that options are
     *        from a previously constructed iterator. Initialization will not be
     *        repeated.
     */
    function RecurIterator(options) {
      _classCallCheck(this, RecurIterator);
      /**
       * True when iteration is finished.
       * @type {Boolean}
       */
      _defineProperty(this, "completed", false);
      /**
       * The rule that is being iterated
       * @type {ICAL.Recur}
       */
      _defineProperty(this, "rule", null);
      /**
       * The start date of the event being iterated.
       * @type {ICAL.Time}
       */
      _defineProperty(this, "dtstart", null);
      /**
       * The last occurrence that was returned from the
       * {@link ICAL.RecurIterator#next} method.
       * @type {ICAL.Time}
       */
      _defineProperty(this, "last", null);
      /**
       * The sequence number from the occurrence
       * @type {Number}
       */
      _defineProperty(this, "occurrence_number", 0);
      /**
       * The indices used for the {@link ICAL.RecurIterator#by_data} object.
       * @type {Object}
       * @private
       */
      _defineProperty(this, "by_indices", null);
      /**
       * If true, the iterator has already been initialized
       * @type {Boolean}
       * @private
       */
      _defineProperty(this, "initialized", false);
      /**
       * The initializd by-data.
       * @type {Object}
       * @private
       */
      _defineProperty(this, "by_data", null);
      /**
       * The expanded yeardays
       * @type {Array}
       * @private
       */
      _defineProperty(this, "days", null);
      /**
       * The index in the {@link ICAL.RecurIterator#days} array.
       * @type {Number}
       * @private
       */
      _defineProperty(this, "days_index", 0);
      this.fromData(options);
    }
    return _createClass(RecurIterator, [{
      key: "fromData",
      value:
      /**
       * Initialize the recurrence iterator from the passed data object. This
       * method is usually not called directly, you can initialize the iterator
       * through the constructor.
       *
       * @param {Object} options                The iterator options
       * @param {ICAL.Recur} options.rule       The rule to iterate.
       * @param {ICAL.Time} options.dtstart     The start date of the event.
       * @param {Boolean=} options.initialized  When true, assume that options are
       *        from a previously constructed iterator. Initialization will not be
       *        repeated.
       */
      function fromData(options) {
        this.rule = formatClassType(options.rule, Recur);
        if (!this.rule) {
          throw new Error('iterator requires a (ICAL.Recur) rule');
        }
        this.dtstart = formatClassType(options.dtstart, Time);
        if (!this.dtstart) {
          throw new Error('iterator requires a (ICAL.Time) dtstart');
        }
        if (options.by_data) {
          this.by_data = options.by_data;
        } else {
          this.by_data = clone(this.rule.parts, true);
        }
        if (options.occurrence_number) this.occurrence_number = options.occurrence_number;
        this.days = options.days || [];
        if (options.last) {
          this.last = formatClassType(options.last, Time);
        }
        this.by_indices = options.by_indices;
        if (!this.by_indices) {
          this.by_indices = {
            "BYSECOND": 0,
            "BYMINUTE": 0,
            "BYHOUR": 0,
            "BYDAY": 0,
            "BYMONTH": 0,
            "BYWEEKNO": 0,
            "BYMONTHDAY": 0
          };
        }
        this.initialized = options.initialized || false;
        if (!this.initialized) {
          this.init();
        }
      }

      /**
       * Initialize the iterator
       * @private
       */
    }, {
      key: "init",
      value: function init() {
        this.initialized = true;
        this.last = this.dtstart.clone();
        var parts = this.by_data;
        if ("BYDAY" in parts) {
          // libical does this earlier when the rule is loaded, but we postpone to
          // now so we can preserve the original order.
          this.sort_byday_rules(parts.BYDAY);
        }

        // If the BYYEARDAY appares, no other date rule part may appear
        if ("BYYEARDAY" in parts) {
          if ("BYMONTH" in parts || "BYWEEKNO" in parts || "BYMONTHDAY" in parts || "BYDAY" in parts) {
            throw new Error("Invalid BYYEARDAY rule");
          }
        }

        // BYWEEKNO and BYMONTHDAY rule parts may not both appear
        if ("BYWEEKNO" in parts && "BYMONTHDAY" in parts) {
          throw new Error("BYWEEKNO does not fit to BYMONTHDAY");
        }

        // For MONTHLY recurrences (FREQ=MONTHLY) neither BYYEARDAY nor
        // BYWEEKNO may appear.
        if (this.rule.freq == "MONTHLY" && ("BYYEARDAY" in parts || "BYWEEKNO" in parts)) {
          throw new Error("For MONTHLY recurrences neither BYYEARDAY nor BYWEEKNO may appear");
        }

        // For WEEKLY recurrences (FREQ=WEEKLY) neither BYMONTHDAY nor
        // BYYEARDAY may appear.
        if (this.rule.freq == "WEEKLY" && ("BYYEARDAY" in parts || "BYMONTHDAY" in parts)) {
          throw new Error("For WEEKLY recurrences neither BYMONTHDAY nor BYYEARDAY may appear");
        }

        // BYYEARDAY may only appear in YEARLY rules
        if (this.rule.freq != "YEARLY" && "BYYEARDAY" in parts) {
          throw new Error("BYYEARDAY may only appear in YEARLY rules");
        }
        this.last.second = this.setup_defaults("BYSECOND", "SECONDLY", this.dtstart.second);
        this.last.minute = this.setup_defaults("BYMINUTE", "MINUTELY", this.dtstart.minute);
        this.last.hour = this.setup_defaults("BYHOUR", "HOURLY", this.dtstart.hour);
        var dayOffset = this.last.day = this.setup_defaults("BYMONTHDAY", "DAILY", this.dtstart.day);
        this.last.month = this.setup_defaults("BYMONTH", "MONTHLY", this.dtstart.month);
        if (this.rule.freq == "WEEKLY") {
          if ("BYDAY" in parts) {
            var _this$ruleDayOfWeek = this.ruleDayOfWeek(parts.BYDAY[0], this.rule.wkst),
              _this$ruleDayOfWeek2 = _slicedToArray(_this$ruleDayOfWeek, 2),
              dow = _this$ruleDayOfWeek2[1];
            var wkdy = dow - this.last.dayOfWeek(this.rule.wkst);
            if (this.last.dayOfWeek(this.rule.wkst) < dow && wkdy >= 0 || wkdy < 0) {
              // Initial time is after first day of BYDAY data
              this.last.day += wkdy;
            }
          } else {
            var dayName = Recur.numericDayToIcalDay(this.dtstart.dayOfWeek());
            parts.BYDAY = [dayName];
          }
        }
        if (this.rule.freq == "YEARLY") {
          for (;;) {
            this.expand_year_days(this.last.year);
            if (this.days.length > 0) {
              break;
            }
            this.increment_year(this.rule.interval);
          }
          this._nextByYearDay();
        }
        if (this.rule.freq == "MONTHLY" && this.has_by_data("BYDAY")) {
          var tempLast = null;
          var initLast = this.last.clone();
          var daysInMonth = Time.daysInMonth(this.last.month, this.last.year);

          // Check every weekday in BYDAY with relative dow and pos.
          var _iterator = _createForOfIteratorHelper(this.by_data.BYDAY),
            _step;
          try {
            for (_iterator.s(); !(_step = _iterator.n()).done;) {
              var bydow = _step.value;
              this.last = initLast.clone();
              var _this$ruleDayOfWeek3 = this.ruleDayOfWeek(bydow),
                _this$ruleDayOfWeek4 = _slicedToArray(_this$ruleDayOfWeek3, 2),
                pos = _this$ruleDayOfWeek4[0],
                _dow = _this$ruleDayOfWeek4[1];
              var dayOfMonth = this.last.nthWeekDay(_dow, pos);

              // If |pos| >= 6, the byday is invalid for a monthly rule.
              if (pos >= 6 || pos <= -6) {
                throw new Error("Malformed values in BYDAY part");
              }

              // If a Byday with pos=+/-5 is not in the current month it
              // must be searched in the next months.
              if (dayOfMonth > daysInMonth || dayOfMonth <= 0) {
                // Skip if we have already found a "last" in this month.
                if (tempLast && tempLast.month == initLast.month) {
                  continue;
                }
                while (dayOfMonth > daysInMonth || dayOfMonth <= 0) {
                  this.increment_month();
                  daysInMonth = Time.daysInMonth(this.last.month, this.last.year);
                  dayOfMonth = this.last.nthWeekDay(_dow, pos);
                }
              }
              this.last.day = dayOfMonth;
              if (!tempLast || this.last.compare(tempLast) < 0) {
                tempLast = this.last.clone();
              }
            }
          } catch (err) {
            _iterator.e(err);
          } finally {
            _iterator.f();
          }
          this.last = tempLast.clone();

          //XXX: This feels like a hack, but we need to initialize
          //     the BYMONTHDAY case correctly and byDayAndMonthDay handles
          //     this case. It accepts a special flag which will avoid incrementing
          //     the initial value without the flag days that match the start time
          //     would be missed.
          if (this.has_by_data('BYMONTHDAY')) {
            this._byDayAndMonthDay(true);
          }
          if (this.last.day > daysInMonth || this.last.day == 0) {
            throw new Error("Malformed values in BYDAY part");
          }
        } else if (this.has_by_data("BYMONTHDAY")) {
          // Attempting to access `this.last.day` will cause the date to be normalised.
          // So it will never be a negative value or more than the number of days in the month.
          // We keep the value in a separate variable instead.

          // Change the day value so that normalisation won't change the month.
          this.last.day = 1;
          var _daysInMonth = Time.daysInMonth(this.last.month, this.last.year);
          if (dayOffset < 0) {
            // A negative value represents days before the end of the month.
            this.last.day = _daysInMonth + dayOffset + 1;
          } else if (this.by_data.BYMONTHDAY[0] > _daysInMonth) {
            // There's no occurrence in this month, find the next valid month.
            // The longest possible sequence of skipped months is February-April-June,
            // so we might need to call next_month up to three times.
            if (!this.next_month() && !this.next_month() && !this.next_month()) {
              throw new Error("No possible occurrences");
            }
          } else {
            // Otherwise, reset the day.
            this.last.day = dayOffset;
          }
        }
      }

      /**
       * Retrieve the next occurrence from the iterator.
       * @return {ICAL.Time}
       */
    }, {
      key: "next",
      value: function next() {
        var before = this.last ? this.last.clone() : null;
        if (this.rule.count && this.occurrence_number >= this.rule.count || this.rule.until && this.last.compare(this.rule.until) > 0) {
          //XXX: right now this is just a flag and has no impact
          //     we can simplify the above case to check for completed later.
          this.completed = true;
          return null;
        }
        if (this.occurrence_number == 0 && this.last.compare(this.dtstart) >= 0) {
          // First of all, give the instance that was initialized
          this.occurrence_number++;
          return this.last;
        }
        var valid;
        do {
          valid = 1;
          switch (this.rule.freq) {
            case "SECONDLY":
              this.next_second();
              break;
            case "MINUTELY":
              this.next_minute();
              break;
            case "HOURLY":
              this.next_hour();
              break;
            case "DAILY":
              this.next_day();
              break;
            case "WEEKLY":
              this.next_week();
              break;
            case "MONTHLY":
              valid = this.next_month();
              break;
            case "YEARLY":
              this.next_year();
              break;
            default:
              return null;
          }
        } while (!this.check_contracting_rules() || this.last.compare(this.dtstart) < 0 || !valid);

        // TODO is this valid?
        if (this.last.compare(before) == 0) {
          throw new Error("Same occurrence found twice, protecting " + "you from death by recursion");
        }
        if (this.rule.until && this.last.compare(this.rule.until) > 0) {
          this.completed = true;
          return null;
        } else {
          this.occurrence_number++;
          return this.last;
        }
      }
    }, {
      key: "next_second",
      value: function next_second() {
        return this.next_generic("BYSECOND", "SECONDLY", "second", "minute");
      }
    }, {
      key: "increment_second",
      value: function increment_second(inc) {
        return this.increment_generic(inc, "second", 60, "minute");
      }
    }, {
      key: "next_minute",
      value: function next_minute() {
        return this.next_generic("BYMINUTE", "MINUTELY", "minute", "hour", "next_second");
      }
    }, {
      key: "increment_minute",
      value: function increment_minute(inc) {
        return this.increment_generic(inc, "minute", 60, "hour");
      }
    }, {
      key: "next_hour",
      value: function next_hour() {
        return this.next_generic("BYHOUR", "HOURLY", "hour", "monthday", "next_minute");
      }
    }, {
      key: "increment_hour",
      value: function increment_hour(inc) {
        this.increment_generic(inc, "hour", 24, "monthday");
      }
    }, {
      key: "next_day",
      value: function next_day() {
        var this_freq = this.rule.freq == "DAILY";
        if (this.next_hour() == 0) {
          return 0;
        }
        if (this_freq) {
          this.increment_monthday(this.rule.interval);
        } else {
          this.increment_monthday(1);
        }
        return 0;
      }
    }, {
      key: "next_week",
      value: function next_week() {
        var end_of_data = 0;
        if (this.next_weekday_by_week() == 0) {
          return end_of_data;
        }
        if (this.has_by_data("BYWEEKNO")) {
          this.by_indices.BYWEEKNO++;
          if (this.by_indices.BYWEEKNO == this.by_data.BYWEEKNO.length) {
            this.by_indices.BYWEEKNO = 0;
            end_of_data = 1;
          }

          // HACK should be first month of the year
          this.last.month = 1;
          this.last.day = 1;
          var week_no = this.by_data.BYWEEKNO[this.by_indices.BYWEEKNO];
          this.last.day += 7 * week_no;
          if (end_of_data) {
            this.increment_year(1);
          }
        } else {
          // Jump to the next week
          this.increment_monthday(7 * this.rule.interval);
        }
        return end_of_data;
      }

      /**
       * Normalize each by day rule for a given year/month.
       * Takes into account ordering and negative rules
       *
       * @private
       * @param {Number} year         Current year.
       * @param {Number} month        Current month.
       * @param {Array}  rules        Array of rules.
       *
       * @return {Array} sorted and normalized rules.
       *                 Negative rules will be expanded to their
       *                 correct positive values for easier processing.
       */
    }, {
      key: "normalizeByMonthDayRules",
      value: function normalizeByMonthDayRules(year, month, rules) {
        var daysInMonth = Time.daysInMonth(month, year);

        // XXX: This is probably bad for performance to allocate
        //      a new array for each month we scan, if possible
        //      we should try to optimize this...
        var newRules = [];
        var ruleIdx = 0;
        var len = rules.length;
        var rule;
        for (; ruleIdx < len; ruleIdx++) {
          rule = rules[ruleIdx];

          // if this rule falls outside of given
          // month discard it.
          if (Math.abs(rule) > daysInMonth) {
            continue;
          }

          // negative case
          if (rule < 0) {
            // we add (not subtract it is a negative number)
            // one from the rule because 1 === last day of month
            rule = daysInMonth + (rule + 1);
          } else if (rule === 0) {
            // skip zero: it is invalid.
            continue;
          }

          // only add unique items...
          if (newRules.indexOf(rule) === -1) {
            newRules.push(rule);
          }
        }

        // unique and sort
        return newRules.sort(function (a, b) {
          return a - b;
        });
      }

      /**
       * NOTES:
       * We are given a list of dates in the month (BYMONTHDAY) (23, etc..)
       * Also we are given a list of days (BYDAY) (MO, 2SU, etc..) when
       * both conditions match a given date (this.last.day) iteration stops.
       *
       * @private
       * @param {Boolean=} isInit     When given true will not increment the
       *                                current day (this.last).
       */
    }, {
      key: "_byDayAndMonthDay",
      value: function _byDayAndMonthDay(isInit) {
        var byMonthDay; // setup in initMonth
        var byDay = this.by_data.BYDAY;
        var date;
        var dateIdx = 0;
        var dateLen; // setup in initMonth
        var dayLen = byDay.length;

        // we are not valid by default
        var dataIsValid = 0;
        var daysInMonth;
        var self = this;
        // we need a copy of this, because a DateTime gets normalized
        // automatically if the day is out of range. At some points we
        // set the last day to 0 to start counting.
        var lastDay = this.last.day;
        function initMonth() {
          daysInMonth = Time.daysInMonth(self.last.month, self.last.year);
          byMonthDay = self.normalizeByMonthDayRules(self.last.year, self.last.month, self.by_data.BYMONTHDAY);
          dateLen = byMonthDay.length;

          // For the case of more than one occurrence in one month
          // we have to be sure to start searching after the last
          // found date or at the last BYMONTHDAY, unless we are
          // initializing the iterator because in this case we have
          // to consider the last found date too.
          while (byMonthDay[dateIdx] <= lastDay && !(isInit && byMonthDay[dateIdx] == lastDay) && dateIdx < dateLen - 1) {
            dateIdx++;
          }
        }
        function nextMonth() {
          // since the day is incremented at the start
          // of the loop below, we need to start at 0
          lastDay = 0;
          self.increment_month();
          dateIdx = 0;
          initMonth();
        }
        initMonth();

        // should come after initMonth
        if (isInit) {
          lastDay -= 1;
        }

        // Use a counter to avoid an infinite loop with malformed rules.
        // Stop checking after 4 years so we consider also a leap year.
        var monthsCounter = 48;
        while (!dataIsValid && monthsCounter) {
          monthsCounter--;
          // increment the current date. This is really
          // important otherwise we may fall into the infinite
          // loop trap. The initial date takes care of the case
          // where the current date is the date we are looking
          // for.
          date = lastDay + 1;
          if (date > daysInMonth) {
            nextMonth();
            continue;
          }

          // find next date
          var next = byMonthDay[dateIdx++];

          // this logic is dependent on the BYMONTHDAYS
          // being in order (which is done by #normalizeByMonthDayRules)
          if (next >= date) {
            // if the next month day is in the future jump to it.
            lastDay = next;
          } else {
            // in this case the 'next' monthday has past
            // we must move to the month.
            nextMonth();
            continue;
          }

          // Now we can loop through the day rules to see
          // if one matches the current month date.
          for (var dayIdx = 0; dayIdx < dayLen; dayIdx++) {
            var parts = this.ruleDayOfWeek(byDay[dayIdx]);
            var pos = parts[0];
            var dow = parts[1];
            this.last.day = lastDay;
            if (this.last.isNthWeekDay(dow, pos)) {
              // when we find the valid one we can mark
              // the conditions as met and break the loop.
              // (Because we have this condition above
              //  it will also break the parent loop).
              dataIsValid = 1;
              break;
            }
          }

          // It is completely possible that the combination
          // cannot be matched in the current month.
          // When we reach the end of possible combinations
          // in the current month we iterate to the next one.
          // since dateIdx is incremented right after getting
          // "next", we don't need dateLen -1 here.
          if (!dataIsValid && dateIdx === dateLen) {
            nextMonth();
            continue;
          }
        }
        if (monthsCounter <= 0) {
          // Checked 4 years without finding a Byday that matches
          // a Bymonthday. Maybe the rule is not correct.
          throw new Error("Malformed values in BYDAY combined with BYMONTHDAY parts");
        }
        return dataIsValid;
      }
    }, {
      key: "next_month",
      value: function next_month() {
        var data_valid = 1;
        if (this.next_hour() == 0) {
          return data_valid;
        }
        if (this.has_by_data("BYDAY") && this.has_by_data("BYMONTHDAY")) {
          data_valid = this._byDayAndMonthDay();
        } else if (this.has_by_data("BYDAY")) {
          var daysInMonth = Time.daysInMonth(this.last.month, this.last.year);
          var setpos = 0;
          var setpos_total = 0;
          if (this.has_by_data("BYSETPOS")) {
            var last_day = this.last.day;
            for (var _day = 1; _day <= daysInMonth; _day++) {
              this.last.day = _day;
              if (this.is_day_in_byday(this.last)) {
                setpos_total++;
                if (_day <= last_day) {
                  setpos++;
                }
              }
            }
            this.last.day = last_day;
          }
          data_valid = 0;
          var day;
          for (day = this.last.day + 1; day <= daysInMonth; day++) {
            this.last.day = day;
            if (this.is_day_in_byday(this.last)) {
              if (!this.has_by_data("BYSETPOS") || this.check_set_position(++setpos) || this.check_set_position(setpos - setpos_total - 1)) {
                data_valid = 1;
                break;
              }
            }
          }
          if (day > daysInMonth) {
            this.last.day = 1;
            this.increment_month();
            if (this.is_day_in_byday(this.last)) {
              if (!this.has_by_data("BYSETPOS") || this.check_set_position(1)) {
                data_valid = 1;
              }
            } else {
              data_valid = 0;
            }
          }
        } else if (this.has_by_data("BYMONTHDAY")) {
          this.by_indices.BYMONTHDAY++;
          if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) {
            this.by_indices.BYMONTHDAY = 0;
            this.increment_month();
          }
          var _daysInMonth2 = Time.daysInMonth(this.last.month, this.last.year);
          var _day2 = this.by_data.BYMONTHDAY[this.by_indices.BYMONTHDAY];
          if (_day2 < 0) {
            _day2 = _daysInMonth2 + _day2 + 1;
          }
          if (_day2 > _daysInMonth2) {
            this.last.day = 1;
            data_valid = this.is_day_in_byday(this.last);
          } else {
            this.last.day = _day2;
          }
        } else {
          this.increment_month();
          var _daysInMonth3 = Time.daysInMonth(this.last.month, this.last.year);
          if (this.by_data.BYMONTHDAY[0] > _daysInMonth3) {
            data_valid = 0;
          } else {
            this.last.day = this.by_data.BYMONTHDAY[0];
          }
        }
        return data_valid;
      }
    }, {
      key: "next_weekday_by_week",
      value: function next_weekday_by_week() {
        var end_of_data = 0;
        if (this.next_hour() == 0) {
          return end_of_data;
        }
        if (!this.has_by_data("BYDAY")) {
          return 1;
        }
        for (;;) {
          var tt = new Time();
          this.by_indices.BYDAY++;
          if (this.by_indices.BYDAY == Object.keys(this.by_data.BYDAY).length) {
            this.by_indices.BYDAY = 0;
            end_of_data = 1;
          }
          var coded_day = this.by_data.BYDAY[this.by_indices.BYDAY];
          var parts = this.ruleDayOfWeek(coded_day);
          var dow = parts[1];
          dow -= this.rule.wkst;
          if (dow < 0) {
            dow += 7;
          }
          tt.year = this.last.year;
          tt.month = this.last.month;
          tt.day = this.last.day;
          var startOfWeek = tt.startDoyWeek(this.rule.wkst);
          if (dow + startOfWeek < 1) {
            // The selected date is in the previous year
            if (!end_of_data) {
              continue;
            }
          }
          var next = Time.fromDayOfYear(startOfWeek + dow, this.last.year);

          /**
           * The normalization horrors below are due to
           * the fact that when the year/month/day changes
           * it can effect the other operations that come after.
           */
          this.last.year = next.year;
          this.last.month = next.month;
          this.last.day = next.day;
          return end_of_data;
        }
      }
    }, {
      key: "next_year",
      value: function next_year() {
        if (this.next_hour() == 0) {
          return 0;
        }
        if (++this.days_index == this.days.length) {
          this.days_index = 0;
          do {
            this.increment_year(this.rule.interval);
            this.expand_year_days(this.last.year);
          } while (this.days.length == 0);
        }
        this._nextByYearDay();
        return 1;
      }
    }, {
      key: "_nextByYearDay",
      value: function _nextByYearDay() {
        var doy = this.days[this.days_index];
        var year = this.last.year;
        if (doy < 1) {
          // Time.fromDayOfYear(doy, year) indexes relative to the
          // start of the given year. That is different from the
          // semantics of BYYEARDAY where negative indexes are an
          // offset from the end of the given year.
          doy += 1;
          year += 1;
        }
        var next = Time.fromDayOfYear(doy, year);
        this.last.day = next.day;
        this.last.month = next.month;
      }

      /**
       * @param dow (eg: '1TU', '-1MO')
       * @param {ICAL.Time.weekDay=} aWeekStart The week start weekday
       * @return [pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart
       */
    }, {
      key: "ruleDayOfWeek",
      value: function ruleDayOfWeek(dow, aWeekStart) {
        var matches = dow.match(/([+-]?[0-9])?(MO|TU|WE|TH|FR|SA|SU)/);
        if (matches) {
          var pos = parseInt(matches[1] || 0, 10);
          dow = Recur.icalDayToNumericDay(matches[2], aWeekStart);
          return [pos, dow];
        } else {
          return [0, 0];
        }
      }
    }, {
      key: "next_generic",
      value: function next_generic(aRuleType, aInterval, aDateAttr, aFollowingAttr, aPreviousIncr) {
        var has_by_rule = (aRuleType in this.by_data);
        var this_freq = this.rule.freq == aInterval;
        var end_of_data = 0;
        if (aPreviousIncr && this[aPreviousIncr]() == 0) {
          return end_of_data;
        }
        if (has_by_rule) {
          this.by_indices[aRuleType]++;
          var dta = this.by_data[aRuleType];
          if (this.by_indices[aRuleType] == dta.length) {
            this.by_indices[aRuleType] = 0;
            end_of_data = 1;
          }
          this.last[aDateAttr] = dta[this.by_indices[aRuleType]];
        } else if (this_freq) {
          this["increment_" + aDateAttr](this.rule.interval);
        }
        if (has_by_rule && end_of_data && this_freq) {
          this["increment_" + aFollowingAttr](1);
        }
        return end_of_data;
      }
    }, {
      key: "increment_monthday",
      value: function increment_monthday(inc) {
        for (var i = 0; i < inc; i++) {
          var daysInMonth = Time.daysInMonth(this.last.month, this.last.year);
          this.last.day++;
          if (this.last.day > daysInMonth) {
            this.last.day -= daysInMonth;
            this.increment_month();
          }
        }
      }
    }, {
      key: "increment_month",
      value: function increment_month() {
        this.last.day = 1;
        if (this.has_by_data("BYMONTH")) {
          this.by_indices.BYMONTH++;
          if (this.by_indices.BYMONTH == this.by_data.BYMONTH.length) {
            this.by_indices.BYMONTH = 0;
            this.increment_year(1);
          }
          this.last.month = this.by_data.BYMONTH[this.by_indices.BYMONTH];
        } else {
          if (this.rule.freq == "MONTHLY") {
            this.last.month += this.rule.interval;
          } else {
            this.last.month++;
          }
          this.last.month--;
          var years = trunc(this.last.month / 12);
          this.last.month %= 12;
          this.last.month++;
          if (years != 0) {
            this.increment_year(years);
          }
        }
      }
    }, {
      key: "increment_year",
      value: function increment_year(inc) {
        this.last.year += inc;
      }
    }, {
      key: "increment_generic",
      value: function increment_generic(inc, aDateAttr, aFactor, aNextIncrement) {
        this.last[aDateAttr] += inc;
        var nextunit = trunc(this.last[aDateAttr] / aFactor);
        this.last[aDateAttr] %= aFactor;
        if (nextunit != 0) {
          this["increment_" + aNextIncrement](nextunit);
        }
      }
    }, {
      key: "has_by_data",
      value: function has_by_data(aRuleType) {
        return aRuleType in this.rule.parts;
      }
    }, {
      key: "expand_year_days",
      value: function expand_year_days(aYear) {
        var t = new Time();
        this.days = [];

        // We need our own copy with a few keys set
        var parts = {};
        var rules = ["BYDAY", "BYWEEKNO", "BYMONTHDAY", "BYMONTH", "BYYEARDAY"];
        for (var _i = 0, _rules = rules; _i < _rules.length; _i++) {
          var part = _rules[_i];
          if (part in this.rule.parts) {
            parts[part] = this.rule.parts[part];
          }
        }
        if ("BYMONTH" in parts && "BYWEEKNO" in parts) {
          var valid = 1;
          var validWeeks = {};
          t.year = aYear;
          t.isDate = true;
          for (var monthIdx = 0; monthIdx < this.by_data.BYMONTH.length; monthIdx++) {
            var month = this.by_data.BYMONTH[monthIdx];
            t.month = month;
            t.day = 1;
            var first_week = t.weekNumber(this.rule.wkst);
            t.day = Time.daysInMonth(month, aYear);
            var last_week = t.weekNumber(this.rule.wkst);
            for (monthIdx = first_week; monthIdx < last_week; monthIdx++) {
              validWeeks[monthIdx] = 1;
            }
          }
          for (var weekIdx = 0; weekIdx < this.by_data.BYWEEKNO.length && valid; weekIdx++) {
            var weekno = this.by_data.BYWEEKNO[weekIdx];
            if (weekno < 52) {
              valid &= validWeeks[weekIdx];
            } else {
              valid = 0;
            }
          }
          if (valid) {
            delete parts.BYMONTH;
          } else {
            delete parts.BYWEEKNO;
          }
        }
        var partCount = Object.keys(parts).length;
        if (partCount == 0) {
          var t1 = this.dtstart.clone();
          t1.year = this.last.year;
          this.days.push(t1.dayOfYear());
        } else if (partCount == 1 && "BYMONTH" in parts) {
          var _iterator2 = _createForOfIteratorHelper(this.by_data.BYMONTH),
            _step2;
          try {
            for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
              var _month = _step2.value;
              var t2 = this.dtstart.clone();
              t2.year = aYear;
              t2.month = _month;
              t2.isDate = true;
              this.days.push(t2.dayOfYear());
            }
          } catch (err) {
            _iterator2.e(err);
          } finally {
            _iterator2.f();
          }
        } else if (partCount == 1 && "BYMONTHDAY" in parts) {
          var _iterator3 = _createForOfIteratorHelper(this.by_data.BYMONTHDAY),
            _step3;
          try {
            for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
              var monthday = _step3.value;
              var t3 = this.dtstart.clone();
              if (monthday < 0) {
                var daysInMonth = Time.daysInMonth(t3.month, aYear);
                monthday = monthday + daysInMonth + 1;
              }
              t3.day = monthday;
              t3.year = aYear;
              t3.isDate = true;
              this.days.push(t3.dayOfYear());
            }
          } catch (err) {
            _iterator3.e(err);
          } finally {
            _iterator3.f();
          }
        } else if (partCount == 2 && "BYMONTHDAY" in parts && "BYMONTH" in parts) {
          var _iterator4 = _createForOfIteratorHelper(this.by_data.BYMONTH),
            _step4;
          try {
            for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
              var _month2 = _step4.value;
              var _daysInMonth4 = Time.daysInMonth(_month2, aYear);
              var _iterator5 = _createForOfIteratorHelper(this.by_data.BYMONTHDAY),
                _step5;
              try {
                for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
                  var _monthday = _step5.value;
                  if (_monthday < 0) {
                    _monthday = _monthday + _daysInMonth4 + 1;
                  }
                  t.day = _monthday;
                  t.month = _month2;
                  t.year = aYear;
                  t.isDate = true;
                  this.days.push(t.dayOfYear());
                }
              } catch (err) {
                _iterator5.e(err);
              } finally {
                _iterator5.f();
              }
            }
          } catch (err) {
            _iterator4.e(err);
          } finally {
            _iterator4.f();
          }
        } else if (partCount == 1 && "BYWEEKNO" in parts) ; else if (partCount == 2 && "BYWEEKNO" in parts && "BYMONTHDAY" in parts) ; else if (partCount == 1 && "BYDAY" in parts) {
          this.days = this.days.concat(this.expand_by_day(aYear));
        } else if (partCount == 2 && "BYDAY" in parts && "BYMONTH" in parts) {
          var _iterator6 = _createForOfIteratorHelper(this.by_data.BYMONTH),
            _step6;
          try {
            for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
              var _month3 = _step6.value;
              var _daysInMonth5 = Time.daysInMonth(_month3, aYear);
              t.year = aYear;
              t.month = _month3;
              t.day = 1;
              t.isDate = true;
              var first_dow = t.dayOfWeek();
              var doy_offset = t.dayOfYear() - 1;
              t.day = _daysInMonth5;
              var last_dow = t.dayOfWeek();
              if (this.has_by_data("BYSETPOS")) {
                var by_month_day = [];
                for (var day = 1; day <= _daysInMonth5; day++) {
                  t.day = day;
                  if (this.is_day_in_byday(t)) {
                    by_month_day.push(day);
                  }
                }
                for (var spIndex = 0; spIndex < by_month_day.length; spIndex++) {
                  if (this.check_set_position(spIndex + 1) || this.check_set_position(spIndex - by_month_day.length)) {
                    this.days.push(doy_offset + by_month_day[spIndex]);
                  }
                }
              } else {
                var _iterator7 = _createForOfIteratorHelper(this.by_data.BYDAY),
                  _step7;
                try {
                  for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
                    var coded_day = _step7.value;
                    var bydayParts = this.ruleDayOfWeek(coded_day);
                    var pos = bydayParts[0];
                    var dow = bydayParts[1];
                    var month_day = void 0;
                    var first_matching_day = (dow + 7 - first_dow) % 7 + 1;
                    var last_matching_day = _daysInMonth5 - (last_dow + 7 - dow) % 7;
                    if (pos == 0) {
                      for (var _day3 = first_matching_day; _day3 <= _daysInMonth5; _day3 += 7) {
                        this.days.push(doy_offset + _day3);
                      }
                    } else if (pos > 0) {
                      month_day = first_matching_day + (pos - 1) * 7;
                      if (month_day <= _daysInMonth5) {
                        this.days.push(doy_offset + month_day);
                      }
                    } else {
                      month_day = last_matching_day + (pos + 1) * 7;
                      if (month_day > 0) {
                        this.days.push(doy_offset + month_day);
                      }
                    }
                  }
                } catch (err) {
                  _iterator7.e(err);
                } finally {
                  _iterator7.f();
                }
              }
            }
            // Return dates in order of occurrence (1,2,3,...) instead
            // of by groups of weekdays (1,8,15,...,2,9,16,...).
          } catch (err) {
            _iterator6.e(err);
          } finally {
            _iterator6.f();
          }
          this.days.sort(function (a, b) {
            return a - b;
          }); // Comparator function allows to sort numbers.
        } else if (partCount == 2 && "BYDAY" in parts && "BYMONTHDAY" in parts) {
          var expandedDays = this.expand_by_day(aYear);
          var _iterator8 = _createForOfIteratorHelper(expandedDays),
            _step8;
          try {
            for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
              var _day4 = _step8.value;
              var tt = Time.fromDayOfYear(_day4, aYear);
              if (this.by_data.BYMONTHDAY.indexOf(tt.day) >= 0) {
                this.days.push(_day4);
              }
            }
          } catch (err) {
            _iterator8.e(err);
          } finally {
            _iterator8.f();
          }
        } else if (partCount == 3 && "BYDAY" in parts && "BYMONTHDAY" in parts && "BYMONTH" in parts) {
          var _expandedDays = this.expand_by_day(aYear);
          var _iterator9 = _createForOfIteratorHelper(_expandedDays),
            _step9;
          try {
            for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
              var _day5 = _step9.value;
              var _tt = Time.fromDayOfYear(_day5, aYear);
              if (this.by_data.BYMONTH.indexOf(_tt.month) >= 0 && this.by_data.BYMONTHDAY.indexOf(_tt.day) >= 0) {
                this.days.push(_day5);
              }
            }
          } catch (err) {
            _iterator9.e(err);
          } finally {
            _iterator9.f();
          }
        } else if (partCount == 2 && "BYDAY" in parts && "BYWEEKNO" in parts) {
          var _expandedDays2 = this.expand_by_day(aYear);
          var _iterator10 = _createForOfIteratorHelper(_expandedDays2),
            _step10;
          try {
            for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
              var _day6 = _step10.value;
              var _tt2 = Time.fromDayOfYear(_day6, aYear);
              var _weekno = _tt2.weekNumber(this.rule.wkst);
              if (this.by_data.BYWEEKNO.indexOf(_weekno)) {
                this.days.push(_day6);
              }
            }
          } catch (err) {
            _iterator10.e(err);
          } finally {
            _iterator10.f();
          }
        } else if (partCount == 3 && "BYDAY" in parts && "BYWEEKNO" in parts && "BYMONTHDAY" in parts) ; else if (partCount == 1 && "BYYEARDAY" in parts) {
          this.days = this.days.concat(this.by_data.BYYEARDAY);
        } else {
          this.days = [];
        }
        return 0;
      }
    }, {
      key: "expand_by_day",
      value: function expand_by_day(aYear) {
        var days_list = [];
        var tmp = this.last.clone();
        tmp.year = aYear;
        tmp.month = 1;
        tmp.day = 1;
        tmp.isDate = true;
        var start_dow = tmp.dayOfWeek();
        tmp.month = 12;
        tmp.day = 31;
        tmp.isDate = true;
        var end_dow = tmp.dayOfWeek();
        var end_year_day = tmp.dayOfYear();
        var _iterator11 = _createForOfIteratorHelper(this.by_data.BYDAY),
          _step11;
        try {
          for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
            var day = _step11.value;
            var parts = this.ruleDayOfWeek(day);
            var pos = parts[0];
            var dow = parts[1];
            if (pos == 0) {
              var tmp_start_doy = (dow + 7 - start_dow) % 7 + 1;
              for (var doy = tmp_start_doy; doy <= end_year_day; doy += 7) {
                days_list.push(doy);
              }
            } else if (pos > 0) {
              var first = void 0;
              if (dow >= start_dow) {
                first = dow - start_dow + 1;
              } else {
                first = dow - start_dow + 8;
              }
              days_list.push(first + (pos - 1) * 7);
            } else {
              var last = void 0;
              pos = -pos;
              if (dow <= end_dow) {
                last = end_year_day - end_dow + dow;
              } else {
                last = end_year_day - end_dow + dow - 7;
              }
              days_list.push(last - (pos - 1) * 7);
            }
          }
        } catch (err) {
          _iterator11.e(err);
        } finally {
          _iterator11.f();
        }
        return days_list;
      }
    }, {
      key: "is_day_in_byday",
      value: function is_day_in_byday(tt) {
        if (this.by_data.BYDAY) {
          var _iterator12 = _createForOfIteratorHelper(this.by_data.BYDAY),
            _step12;
          try {
            for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
              var day = _step12.value;
              var parts = this.ruleDayOfWeek(day);
              var pos = parts[0];
              var dow = parts[1];
              var this_dow = tt.dayOfWeek();
              if (pos == 0 && dow == this_dow || tt.nthWeekDay(dow, pos) == tt.day) {
                return 1;
              }
            }
          } catch (err) {
            _iterator12.e(err);
          } finally {
            _iterator12.f();
          }
        }
        return 0;
      }

      /**
       * Checks if given value is in BYSETPOS.
       *
       * @private
       * @param {Numeric} aPos position to check for.
       * @return {Boolean} false unless BYSETPOS rules exist
       *                   and the given value is present in rules.
       */
    }, {
      key: "check_set_position",
      value: function check_set_position(aPos) {
        if (this.has_by_data('BYSETPOS')) {
          var idx = this.by_data.BYSETPOS.indexOf(aPos);
          // negative numbers are not false-y
          return idx !== -1;
        }
        return false;
      }
    }, {
      key: "sort_byday_rules",
      value: function sort_byday_rules(aRules) {
        for (var i = 0; i < aRules.length; i++) {
          for (var j = 0; j < i; j++) {
            var one = this.ruleDayOfWeek(aRules[j], this.rule.wkst)[1];
            var two = this.ruleDayOfWeek(aRules[i], this.rule.wkst)[1];
            if (one > two) {
              var tmp = aRules[i];
              aRules[i] = aRules[j];
              aRules[j] = tmp;
            }
          }
        }
      }
    }, {
      key: "check_contract_restriction",
      value: function check_contract_restriction(aRuleType, v) {
        var indexMapValue = RecurIterator._indexMap[aRuleType];
        var ruleMapValue = RecurIterator._expandMap[this.rule.freq][indexMapValue];
        var pass = false;
        if (aRuleType in this.by_data && ruleMapValue == RecurIterator.CONTRACT) {
          var ruleType = this.by_data[aRuleType];
          var _iterator13 = _createForOfIteratorHelper(ruleType),
            _step13;
          try {
            for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
              var bydata = _step13.value;
              if (bydata == v) {
                pass = true;
                break;
              }
            }
          } catch (err) {
            _iterator13.e(err);
          } finally {
            _iterator13.f();
          }
        } else {
          // Not a contracting byrule or has no data, test passes
          pass = true;
        }
        return pass;
      }
    }, {
      key: "check_contracting_rules",
      value: function check_contracting_rules() {
        var dow = this.last.dayOfWeek();
        var weekNo = this.last.weekNumber(this.rule.wkst);
        var doy = this.last.dayOfYear();
        return this.check_contract_restriction("BYSECOND", this.last.second) && this.check_contract_restriction("BYMINUTE", this.last.minute) && this.check_contract_restriction("BYHOUR", this.last.hour) && this.check_contract_restriction("BYDAY", Recur.numericDayToIcalDay(dow)) && this.check_contract_restriction("BYWEEKNO", weekNo) && this.check_contract_restriction("BYMONTHDAY", this.last.day) && this.check_contract_restriction("BYMONTH", this.last.month) && this.check_contract_restriction("BYYEARDAY", doy);
      }
    }, {
      key: "setup_defaults",
      value: function setup_defaults(aRuleType, req, deftime) {
        var indexMapValue = RecurIterator._indexMap[aRuleType];
        var ruleMapValue = RecurIterator._expandMap[this.rule.freq][indexMapValue];
        if (ruleMapValue != RecurIterator.CONTRACT) {
          if (!(aRuleType in this.by_data)) {
            this.by_data[aRuleType] = [deftime];
          }
          if (this.rule.freq != req) {
            return this.by_data[aRuleType][0];
          }
        }
        return deftime;
      }

      /**
       * Convert iterator into a serialize-able object.  Will preserve current
       * iteration sequence to ensure the seamless continuation of the recurrence
       * rule.
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        var result = Object.create(null);
        result.initialized = this.initialized;
        result.rule = this.rule.toJSON();
        result.dtstart = this.dtstart.toJSON();
        result.by_data = this.by_data;
        result.days = this.days;
        result.last = this.last.toJSON();
        result.by_indices = this.by_indices;
        result.occurrence_number = this.occurrence_number;
        return result;
      }
    }]);
  }();
  _defineProperty(RecurIterator, "_indexMap", {
    "BYSECOND": 0,
    "BYMINUTE": 1,
    "BYHOUR": 2,
    "BYDAY": 3,
    "BYMONTHDAY": 4,
    "BYYEARDAY": 5,
    "BYWEEKNO": 6,
    "BYMONTH": 7,
    "BYSETPOS": 8
  });
  _defineProperty(RecurIterator, "_expandMap", {
    "SECONDLY": [1, 1, 1, 1, 1, 1, 1, 1],
    "MINUTELY": [2, 1, 1, 1, 1, 1, 1, 1],
    "HOURLY": [2, 2, 1, 1, 1, 1, 1, 1],
    "DAILY": [2, 2, 2, 1, 1, 1, 1, 1],
    "WEEKLY": [2, 2, 2, 2, 3, 3, 1, 1],
    "MONTHLY": [2, 2, 2, 2, 2, 3, 3, 1],
    "YEARLY": [2, 2, 2, 2, 2, 2, 2, 2]
  });
  _defineProperty(RecurIterator, "UNKNOWN", 0);
  _defineProperty(RecurIterator, "CONTRACT", 1);
  _defineProperty(RecurIterator, "EXPAND", 2);
  _defineProperty(RecurIterator, "ILLEGAL", 3);

  var VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/;
  var VALID_BYDAY_PART = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/;
  var DOW_MAP = {
    SU: Time.SUNDAY,
    MO: Time.MONDAY,
    TU: Time.TUESDAY,
    WE: Time.WEDNESDAY,
    TH: Time.THURSDAY,
    FR: Time.FRIDAY,
    SA: Time.SATURDAY
  };
  var REVERSE_DOW_MAP = Object.fromEntries(Object.entries(DOW_MAP).map(function (entry) {
    return entry.reverse();
  }));

  /**
   * Possible frequency values for the FREQ part
   * (YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY)
   *
   * @typedef {String} frequencyValues
   * @memberof ICAL.Recur
   */

  var ALLOWED_FREQ = ['SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'];

  /**
   * This class represents the "recur" value type, used for example by RRULE. It provides methods to
   * calculate occurrences among others.
   *
   * @class
   * @alias ICAL.Recur
   */
  var Recur = /*#__PURE__*/function () {
    /**
     * Create a new instance of the Recur class.
     *
     * @param {Object} data                               An object with members of the recurrence
     * @param {ICAL.Recur.frequencyValues=} data.freq     The frequency value
     * @param {Number=} data.interval                     The INTERVAL value
     * @param {ICAL.Time.weekDay=} data.wkst              The week start value
     * @param {ICAL.Time=} data.until                     The end of the recurrence set
     * @param {Number=} data.count                        The number of occurrences
     * @param {Array.<Number>=} data.bysecond             The seconds for the BYSECOND part
     * @param {Array.<Number>=} data.byminute             The minutes for the BYMINUTE part
     * @param {Array.<Number>=} data.byhour               The hours for the BYHOUR part
     * @param {Array.<String>=} data.byday                The BYDAY values
     * @param {Array.<Number>=} data.bymonthday           The days for the BYMONTHDAY part
     * @param {Array.<Number>=} data.byyearday            The days for the BYYEARDAY part
     * @param {Array.<Number>=} data.byweekno             The weeks for the BYWEEKNO part
     * @param {Array.<Number>=} data.bymonth              The month for the BYMONTH part
     * @param {Array.<Number>=} data.bysetpos             The positionals for the BYSETPOS part
     */
    function Recur(data) {
      _classCallCheck(this, Recur);
      /**
       * An object holding the BY-parts of the recurrence rule
       * @type {Object}
       */
      _defineProperty(this, "parts", null);
      /**
       * The interval value for the recurrence rule.
       * @type {Number}
       */
      _defineProperty(this, "interval", 1);
      /**
       * The week start day
       *
       * @type {ICAL.Time.weekDay}
       * @default ICAL.Time.MONDAY
       */
      _defineProperty(this, "wkst", Time.MONDAY);
      /**
       * The end of the recurrence
       * @type {?ICAL.Time}
       */
      _defineProperty(this, "until", null);
      /**
       * The maximum number of occurrences
       * @type {?Number}
       */
      _defineProperty(this, "count", null);
      /**
       * The frequency value.
       * @type {ICAL.Recur.frequencyValues}
       */
      _defineProperty(this, "freq", null);
      /**
       * The class identifier.
       * @constant
       * @type {String}
       * @default "icalrecur"
       */
      _defineProperty(this, "icalclass", "icalrecur");
      /**
       * The type name, to be used in the jCal object.
       * @constant
       * @type {String}
       * @default "recur"
       */
      _defineProperty(this, "icaltype", "recur");
      this.wrappedJSObject = this;
      this.parts = {};
      if (data && _typeof(data) === 'object') {
        this.fromData(data);
      }
    }
    return _createClass(Recur, [{
      key: "iterator",
      value:
      /**
       * Create a new iterator for this recurrence rule. The passed start date
       * must be the start date of the event, not the start of the range to
       * search in.
       *
       * @example
       * let recur = comp.getFirstPropertyValue('rrule');
       * let dtstart = comp.getFirstPropertyValue('dtstart');
       * let iter = recur.iterator(dtstart);
       * for (let next = iter.next(); next; next = iter.next()) {
       *   if (next.compare(rangeStart) < 0) {
       *     continue;
       *   }
       *   console.log(next.toString());
       * }
       *
       * @param {ICAL.Time} aStart        The item's start date
       * @return {ICAL.RecurIterator}     The recurrence iterator
       */
      function iterator(aStart) {
        return new RecurIterator({
          rule: this,
          dtstart: aStart
        });
      }

      /**
       * Returns a clone of the recurrence object.
       *
       * @return {ICAL.Recur}      The cloned object
       */
    }, {
      key: "clone",
      value: function clone() {
        return new Recur(this.toJSON());
      }

      /**
       * Checks if the current rule is finite, i.e. has a count or until part.
       *
       * @return {Boolean}        True, if the rule is finite
       */
    }, {
      key: "isFinite",
      value: function isFinite() {
        return !!(this.count || this.until);
      }

      /**
       * Checks if the current rule has a count part, and not limited by an until
       * part.
       *
       * @return {Boolean}        True, if the rule is by count
       */
    }, {
      key: "isByCount",
      value: function isByCount() {
        return !!(this.count && !this.until);
      }

      /**
       * Adds a component (part) to the recurrence rule. This is not a component
       * in the sense of {@link ICAL.Component}, but a part of the recurrence
       * rule, i.e. BYMONTH.
       *
       * @param {String} aType            The name of the component part
       * @param {Array|String} aValue     The component value
       */
    }, {
      key: "addComponent",
      value: function addComponent(aType, aValue) {
        var ucname = aType.toUpperCase();
        if (ucname in this.parts) {
          this.parts[ucname].push(aValue);
        } else {
          this.parts[ucname] = [aValue];
        }
      }

      /**
       * Sets the component value for the given by-part.
       *
       * @param {String} aType        The component part name
       * @param {Array} aValues       The component values
       */
    }, {
      key: "setComponent",
      value: function setComponent(aType, aValues) {
        this.parts[aType.toUpperCase()] = aValues.slice();
      }

      /**
       * Gets (a copy) of the requested component value.
       *
       * @param {String} aType        The component part name
       * @return {Array}              The component part value
       */
    }, {
      key: "getComponent",
      value: function getComponent(aType) {
        var ucname = aType.toUpperCase();
        return ucname in this.parts ? this.parts[ucname].slice() : [];
      }

      /**
       * Retrieves the next occurrence after the given recurrence id. See the
       * guide on {@tutorial terminology} for more details.
       *
       * NOTE: Currently, this method iterates all occurrences from the start
       * date. It should not be called in a loop for performance reasons. If you
       * would like to get more than one occurrence, you can iterate the
       * occurrences manually, see the example on the
       * {@link ICAL.Recur#iterator iterator} method.
       *
       * @param {ICAL.Time} aStartTime        The start of the event series
       * @param {ICAL.Time} aRecurrenceId     The date of the last occurrence
       * @return {ICAL.Time}                  The next occurrence after
       */
    }, {
      key: "getNextOccurrence",
      value: function getNextOccurrence(aStartTime, aRecurrenceId) {
        var iter = this.iterator(aStartTime);
        var next;
        do {
          next = iter.next();
        } while (next && next.compare(aRecurrenceId) <= 0);
        if (next && aRecurrenceId.zone) {
          next.zone = aRecurrenceId.zone;
        }
        return next;
      }

      /**
       * Sets up the current instance using members from the passed data object.
       *
       * @param {Object} data                               An object with members of the recurrence
       * @param {ICAL.Recur.frequencyValues=} data.freq     The frequency value
       * @param {Number=} data.interval                     The INTERVAL value
       * @param {ICAL.Time.weekDay=} data.wkst              The week start value
       * @param {ICAL.Time=} data.until                     The end of the recurrence set
       * @param {Number=} data.count                        The number of occurrences
       * @param {Array.<Number>=} data.bysecond             The seconds for the BYSECOND part
       * @param {Array.<Number>=} data.byminute             The minutes for the BYMINUTE part
       * @param {Array.<Number>=} data.byhour               The hours for the BYHOUR part
       * @param {Array.<String>=} data.byday                The BYDAY values
       * @param {Array.<Number>=} data.bymonthday           The days for the BYMONTHDAY part
       * @param {Array.<Number>=} data.byyearday            The days for the BYYEARDAY part
       * @param {Array.<Number>=} data.byweekno             The weeks for the BYWEEKNO part
       * @param {Array.<Number>=} data.bymonth              The month for the BYMONTH part
       * @param {Array.<Number>=} data.bysetpos             The positionals for the BYSETPOS part
       */
    }, {
      key: "fromData",
      value: function fromData(data) {
        for (var key in data) {
          var uckey = key.toUpperCase();
          if (uckey in partDesign) {
            if (Array.isArray(data[key])) {
              this.parts[uckey] = data[key];
            } else {
              this.parts[uckey] = [data[key]];
            }
          } else {
            this[key] = data[key];
          }
        }
        if (this.interval && typeof this.interval != "number") {
          optionDesign.INTERVAL(this.interval, this);
        }
        if (this.wkst && typeof this.wkst != "number") {
          this.wkst = Recur.icalDayToNumericDay(this.wkst);
        }
        if (this.until && !(this.until instanceof Time)) {
          this.until = Time.fromString(this.until);
        }
      }

      /**
       * The jCal representation of this recurrence type.
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        var res = Object.create(null);
        res.freq = this.freq;
        if (this.count) {
          res.count = this.count;
        }
        if (this.interval > 1) {
          res.interval = this.interval;
        }
        for (var _i = 0, _Object$entries = Object.entries(this.parts); _i < _Object$entries.length; _i++) {
          var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
            k = _Object$entries$_i[0],
            kparts = _Object$entries$_i[1];
          if (Array.isArray(kparts) && kparts.length == 1) {
            res[k.toLowerCase()] = kparts[0];
          } else {
            res[k.toLowerCase()] = clone(kparts);
          }
        }
        if (this.until) {
          res.until = this.until.toString();
        }
        if ('wkst' in this && this.wkst !== Time.DEFAULT_WEEK_START) {
          res.wkst = Recur.numericDayToIcalDay(this.wkst);
        }
        return res;
      }

      /**
       * The string representation of this recurrence rule.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        // TODO retain order
        var str = "FREQ=" + this.freq;
        if (this.count) {
          str += ";COUNT=" + this.count;
        }
        if (this.interval > 1) {
          str += ";INTERVAL=" + this.interval;
        }
        for (var _i2 = 0, _Object$entries2 = Object.entries(this.parts); _i2 < _Object$entries2.length; _i2++) {
          var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
            k = _Object$entries2$_i[0],
            v = _Object$entries2$_i[1];
          str += ";" + k + "=" + v;
        }
        if (this.until) {
          str += ';UNTIL=' + this.until.toICALString();
        }
        if ('wkst' in this && this.wkst !== Time.DEFAULT_WEEK_START) {
          str += ';WKST=' + Recur.numericDayToIcalDay(this.wkst);
        }
        return str;
      }
    }], [{
      key: "fromString",
      value:
      /**
       * Creates a new {@link ICAL.Recur} instance from the passed string.
       *
       * @param {String} string         The string to parse
       * @return {ICAL.Recur}           The created recurrence instance
       */
      function fromString(string) {
        var data = this._stringToData(string, false);
        return new Recur(data);
      }

      /**
       * Creates a new {@link ICAL.Recur} instance using members from the passed
       * data object.
       *
       * @param {Object} aData                              An object with members of the recurrence
       * @param {ICAL.Recur.frequencyValues=} aData.freq    The frequency value
       * @param {Number=} aData.interval                    The INTERVAL value
       * @param {ICAL.Time.weekDay=} aData.wkst             The week start value
       * @param {ICAL.Time=} aData.until                    The end of the recurrence set
       * @param {Number=} aData.count                       The number of occurrences
       * @param {Array.<Number>=} aData.bysecond            The seconds for the BYSECOND part
       * @param {Array.<Number>=} aData.byminute            The minutes for the BYMINUTE part
       * @param {Array.<Number>=} aData.byhour              The hours for the BYHOUR part
       * @param {Array.<String>=} aData.byday               The BYDAY values
       * @param {Array.<Number>=} aData.bymonthday          The days for the BYMONTHDAY part
       * @param {Array.<Number>=} aData.byyearday           The days for the BYYEARDAY part
       * @param {Array.<Number>=} aData.byweekno            The weeks for the BYWEEKNO part
       * @param {Array.<Number>=} aData.bymonth             The month for the BYMONTH part
       * @param {Array.<Number>=} aData.bysetpos            The positionals for the BYSETPOS part
       */
    }, {
      key: "fromData",
      value: function fromData(aData) {
        return new Recur(aData);
      }

      /**
       * Converts a recurrence string to a data object, suitable for the fromData
       * method.
       *
       * @private
       * @param {String} string     The string to parse
       * @param {Boolean} fmtIcal   If true, the string is considered to be an
       *                              iCalendar string
       * @return {ICAL.Recur}       The recurrence instance
       */
    }, {
      key: "_stringToData",
      value: function _stringToData(string, fmtIcal) {
        var dict = Object.create(null);

        // split is slower in FF but fast enough.
        // v8 however this is faster then manual split?
        var values = string.split(';');
        var len = values.length;
        for (var i = 0; i < len; i++) {
          var parts = values[i].split('=');
          var ucname = parts[0].toUpperCase();
          var lcname = parts[0].toLowerCase();
          var name = fmtIcal ? lcname : ucname;
          var value = parts[1];
          if (ucname in partDesign) {
            var partArr = value.split(',');
            var partArrIdx = 0;
            var partArrLen = partArr.length;
            for (; partArrIdx < partArrLen; partArrIdx++) {
              partArr[partArrIdx] = partDesign[ucname](partArr[partArrIdx]);
            }
            dict[name] = partArr.length == 1 ? partArr[0] : partArr;
          } else if (ucname in optionDesign) {
            optionDesign[ucname](value, dict, fmtIcal);
          } else {
            // Don't swallow unknown values. Just set them as they are.
            dict[lcname] = value;
          }
        }
        return dict;
      }

      /**
       * Convert an ical representation of a day (SU, MO, etc..)
       * into a numeric value of that day.
       *
       * @param {String} string     The iCalendar day name
       * @param {ICAL.Time.weekDay=} aWeekStart
       *        The week start weekday, defaults to SUNDAY
       * @return {Number}           Numeric value of given day
       */
    }, {
      key: "icalDayToNumericDay",
      value: function icalDayToNumericDay(string, aWeekStart) {
        //XXX: this is here so we can deal
        //     with possibly invalid string values.
        var firstDow = aWeekStart || Time.SUNDAY;
        return (DOW_MAP[string] - firstDow + 7) % 7 + 1;
      }

      /**
       * Convert a numeric day value into its ical representation (SU, MO, etc..)
       *
       * @param {Number} num        Numeric value of given day
       * @param {ICAL.Time.weekDay=} aWeekStart
       *        The week start weekday, defaults to SUNDAY
       * @return {String}           The ICAL day value, e.g SU,MO,...
       */
    }, {
      key: "numericDayToIcalDay",
      value: function numericDayToIcalDay(num, aWeekStart) {
        //XXX: this is here so we can deal with possibly invalid number values.
        //     Also, this allows consistent mapping between day numbers and day
        //     names for external users.
        var firstDow = aWeekStart || Time.SUNDAY;
        var dow = num + firstDow - Time.SUNDAY;
        if (dow > 7) {
          dow -= 7;
        }
        return REVERSE_DOW_MAP[dow];
      }
    }]);
  }();
  function parseNumericValue(type, min, max, value) {
    var result = value;
    if (value[0] === '+') {
      result = value.slice(1);
    }
    result = strictParseInt(result);
    if (min !== undefined && value < min) {
      throw new Error(type + ': invalid value "' + value + '" must be > ' + min);
    }
    if (max !== undefined && value > max) {
      throw new Error(type + ': invalid value "' + value + '" must be < ' + min);
    }
    return result;
  }
  var optionDesign = {
    FREQ: function FREQ(value, dict, fmtIcal) {
      // yes this is actually equal or faster then regex.
      // upside here is we can enumerate the valid values.
      if (ALLOWED_FREQ.indexOf(value) !== -1) {
        dict.freq = value;
      } else {
        throw new Error('invalid frequency "' + value + '" expected: "' + ALLOWED_FREQ.join(', ') + '"');
      }
    },
    COUNT: function COUNT(value, dict, fmtIcal) {
      dict.count = strictParseInt(value);
    },
    INTERVAL: function INTERVAL(value, dict, fmtIcal) {
      dict.interval = strictParseInt(value);
      if (dict.interval < 1) {
        // 0 or negative values are not allowed, some engines seem to generate
        // it though. Assume 1 instead.
        dict.interval = 1;
      }
    },
    UNTIL: function UNTIL(value, dict, fmtIcal) {
      if (value.length > 10) {
        dict.until = design.icalendar.value['date-time'].fromICAL(value);
      } else {
        dict.until = design.icalendar.value.date.fromICAL(value);
      }
      if (!fmtIcal) {
        dict.until = Time.fromString(dict.until);
      }
    },
    WKST: function WKST(value, dict, fmtIcal) {
      if (VALID_DAY_NAMES.test(value)) {
        dict.wkst = Recur.icalDayToNumericDay(value);
      } else {
        throw new Error('invalid WKST value "' + value + '"');
      }
    }
  };
  var partDesign = {
    BYSECOND: parseNumericValue.bind(undefined, 'BYSECOND', 0, 60),
    BYMINUTE: parseNumericValue.bind(undefined, 'BYMINUTE', 0, 59),
    BYHOUR: parseNumericValue.bind(undefined, 'BYHOUR', 0, 23),
    BYDAY: function BYDAY(value) {
      if (VALID_BYDAY_PART.test(value)) {
        return value;
      } else {
        throw new Error('invalid BYDAY value "' + value + '"');
      }
    },
    BYMONTHDAY: parseNumericValue.bind(undefined, 'BYMONTHDAY', -31, 31),
    BYYEARDAY: parseNumericValue.bind(undefined, 'BYYEARDAY', -366, 366),
    BYWEEKNO: parseNumericValue.bind(undefined, 'BYWEEKNO', -53, 53),
    BYMONTH: parseNumericValue.bind(undefined, 'BYMONTH', 1, 12),
    BYSETPOS: parseNumericValue.bind(undefined, 'BYSETPOS', -366, 366)
  };

  /**
   * This class represents the "period" value type, with various calculation and manipulation methods.
   *
   * @class
   * @alias ICAL.Period
   */
  var Period = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Period instance. The passed data object cannot contain both and end date and
     * a duration.
     *
     * @param {Object} aData                  An object with members of the period
     * @param {ICAL.Time=} aData.start        The start of the period
     * @param {ICAL.Time=} aData.end          The end of the period
     * @param {ICAL.Duration=} aData.duration The duration of the period
     */
    function Period(aData) {
      _classCallCheck(this, Period);
      /**
       * The start of the period
       * @type {ICAL.Time}
       */
      _defineProperty(this, "start", null);
      /**
       * The end of the period
       * @type {ICAL.Time}
       */
      _defineProperty(this, "end", null);
      /**
       * The duration of the period
       * @type {ICAL.Duration}
       */
      _defineProperty(this, "duration", null);
      /**
       * The class identifier.
       * @constant
       * @type {String}
       * @default "icalperiod"
       */
      _defineProperty(this, "icalclass", "icalperiod");
      /**
       * The type name, to be used in the jCal object.
       * @constant
       * @type {String}
       * @default "period"
       */
      _defineProperty(this, "icaltype", "period");
      this.wrappedJSObject = this;
      if (aData && 'start' in aData) {
        if (aData.start && !(aData.start instanceof Time)) {
          throw new TypeError('.start must be an instance of ICAL.Time');
        }
        this.start = aData.start;
      }
      if (aData && aData.end && aData.duration) {
        throw new Error('cannot accept both end and duration');
      }
      if (aData && 'end' in aData) {
        if (aData.end && !(aData.end instanceof Time)) {
          throw new TypeError('.end must be an instance of ICAL.Time');
        }
        this.end = aData.end;
      }
      if (aData && 'duration' in aData) {
        if (aData.duration && !(aData.duration instanceof Duration)) {
          throw new TypeError('.duration must be an instance of ICAL.Duration');
        }
        this.duration = aData.duration;
      }
    }
    return _createClass(Period, [{
      key: "clone",
      value:
      /**
       * Returns a clone of the duration object.
       *
       * @return {ICAL.Period}      The cloned object
       */
      function clone() {
        return Period.fromData({
          start: this.start ? this.start.clone() : null,
          end: this.end ? this.end.clone() : null,
          duration: this.duration ? this.duration.clone() : null
        });
      }

      /**
       * Calculates the duration of the period, either directly or by subtracting
       * start from end date.
       *
       * @return {ICAL.Duration}      The calculated duration
       */
    }, {
      key: "getDuration",
      value: function getDuration() {
        if (this.duration) {
          return this.duration;
        } else {
          return this.end.subtractDate(this.start);
        }
      }

      /**
       * Calculates the end date of the period, either directly or by adding
       * duration to start date.
       *
       * @return {ICAL.Time}          The calculated end date
       */
    }, {
      key: "getEnd",
      value: function getEnd() {
        if (this.end) {
          return this.end;
        } else {
          var end = this.start.clone();
          end.addDuration(this.duration);
          return end;
        }
      }

      /**
       * The string representation of this period.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        return this.start + "/" + (this.end || this.duration);
      }

      /**
       * The jCal representation of this period type.
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        return [this.start.toString(), (this.end || this.duration).toString()];
      }

      /**
       * The iCalendar string representation of this period.
       * @return {String}
       */
    }, {
      key: "toICALString",
      value: function toICALString() {
        return this.start.toICALString() + "/" + (this.end || this.duration).toICALString();
      }
    }], [{
      key: "fromString",
      value:
      /**
       * Creates a new {@link ICAL.Period} instance from the passed string.
       *
       * @param {String} str            The string to parse
       * @param {ICAL.Property} prop    The property this period will be on
       * @return {ICAL.Period}          The created period instance
       */
      function fromString(str, prop) {
        var parts = str.split('/');
        if (parts.length !== 2) {
          throw new Error('Invalid string value: "' + str + '" must contain a "/" char.');
        }
        var options = {
          start: Time.fromDateTimeString(parts[0], prop)
        };
        var end = parts[1];
        if (Duration.isValueString(end)) {
          options.duration = Duration.fromString(end);
        } else {
          options.end = Time.fromDateTimeString(end, prop);
        }
        return new Period(options);
      }

      /**
       * Creates a new {@link ICAL.Period} instance from the given data object.
       * The passed data object cannot contain both and end date and a duration.
       *
       * @param {Object} aData                  An object with members of the period
       * @param {ICAL.Time=} aData.start        The start of the period
       * @param {ICAL.Time=} aData.end          The end of the period
       * @param {ICAL.Duration=} aData.duration The duration of the period
       * @return {ICAL.Period}                  The period instance
       */
    }, {
      key: "fromData",
      value: function fromData(aData) {
        return new Period(aData);
      }

      /**
       * Returns a new period instance from the given jCal data array. The first
       * member is always the start date string, the second member is either a
       * duration or end date string.
       *
       * @param {Array<String,String>} aData    The jCal data array
       * @param {ICAL.Property} aProp           The property this jCal data is on
       * @param {Boolean} aLenient              If true, data value can be both date and date-time
       * @return {ICAL.Period}                  The period instance
       */
    }, {
      key: "fromJSON",
      value: function fromJSON(aData, aProp, aLenient) {
        function fromDateOrDateTimeString(aValue, dateProp) {
          if (aLenient) {
            return Time.fromString(aValue, dateProp);
          } else {
            return Time.fromDateTimeString(aValue, dateProp);
          }
        }
        if (Duration.isValueString(aData[1])) {
          return Period.fromData({
            start: fromDateOrDateTimeString(aData[0], aProp),
            duration: Duration.fromString(aData[1])
          });
        } else {
          return Period.fromData({
            start: fromDateOrDateTimeString(aData[0], aProp),
            end: fromDateOrDateTimeString(aData[1], aProp)
          });
        }
      }
    }]);
  }();

  /** @module ICAL.design */

  var FROM_ICAL_NEWLINE = /\\\\|\\;|\\,|\\[Nn]/g;
  var TO_ICAL_NEWLINE = /\\|;|,|\n/g;
  var FROM_VCARD_NEWLINE = /\\\\|\\,|\\[Nn]/g;
  var TO_VCARD_NEWLINE = /\\|,|\n/g;
  function createTextType(fromNewline, toNewline) {
    var result = {
      matches: /.*/,
      fromICAL: function fromICAL(aValue, structuredEscape) {
        return replaceNewline(aValue, fromNewline, structuredEscape);
      },
      toICAL: function toICAL(aValue, structuredEscape) {
        var regEx = toNewline;
        if (structuredEscape) regEx = new RegExp(regEx.source + '|' + structuredEscape, regEx.flags);
        return aValue.replace(regEx, function (str) {
          switch (str) {
            case "\\":
              return "\\\\";
            case ";":
              return "\\;";
            case ",":
              return "\\,";
            case "\n":
              return "\\n";
            /* c8 ignore next 2 */
            default:
              return str;
          }
        });
      }
    };
    return result;
  }

  // default types used multiple times
  var DEFAULT_TYPE_TEXT = {
    defaultType: "text"
  };
  var DEFAULT_TYPE_TEXT_MULTI = {
    defaultType: "text",
    multiValue: ","
  };
  var DEFAULT_TYPE_TEXT_STRUCTURED = {
    defaultType: "text",
    structuredValue: ";"
  };
  var DEFAULT_TYPE_INTEGER = {
    defaultType: "integer"
  };
  var DEFAULT_TYPE_DATETIME_DATE = {
    defaultType: "date-time",
    allowedTypes: ["date-time", "date"]
  };
  var DEFAULT_TYPE_DATETIME = {
    defaultType: "date-time"
  };
  var DEFAULT_TYPE_URI = {
    defaultType: "uri"
  };
  var DEFAULT_TYPE_UTCOFFSET = {
    defaultType: "utc-offset"
  };
  var DEFAULT_TYPE_RECUR = {
    defaultType: "recur"
  };
  var DEFAULT_TYPE_DATE_ANDOR_TIME = {
    defaultType: "date-and-or-time",
    allowedTypes: ["date-time", "date", "text"]
  };
  function replaceNewlineReplace(string) {
    switch (string) {
      case "\\\\":
        return "\\";
      case "\\;":
        return ";";
      case "\\,":
        return ",";
      case "\\n":
      case "\\N":
        return "\n";
      /* c8 ignore next 2 */
      default:
        return string;
    }
  }
  function replaceNewline(value, newline, structuredEscape) {
    // avoid regex when possible.
    if (value.indexOf('\\') === -1) {
      return value;
    }
    if (structuredEscape) newline = new RegExp(newline.source + '|\\\\' + structuredEscape, newline.flags);
    return value.replace(newline, replaceNewlineReplace);
  }
  var commonProperties = {
    "categories": DEFAULT_TYPE_TEXT_MULTI,
    "url": DEFAULT_TYPE_URI,
    "version": DEFAULT_TYPE_TEXT,
    "uid": DEFAULT_TYPE_TEXT
  };
  var commonValues = {
    "boolean": {
      values: ["TRUE", "FALSE"],
      fromICAL: function fromICAL(aValue) {
        switch (aValue) {
          case 'TRUE':
            return true;
          case 'FALSE':
            return false;
          default:
            //TODO: parser warning
            return false;
        }
      },
      toICAL: function toICAL(aValue) {
        if (aValue) {
          return 'TRUE';
        }
        return 'FALSE';
      }
    },
    "float": {
      matches: /^[+-]?\d+\.\d+$/,
      fromICAL: function fromICAL(aValue) {
        var parsed = parseFloat(aValue);
        if (isStrictlyNaN(parsed)) {
          // TODO: parser warning
          return 0.0;
        }
        return parsed;
      },
      toICAL: function toICAL(aValue) {
        return String(aValue);
      }
    },
    integer: {
      fromICAL: function fromICAL(aValue) {
        var parsed = parseInt(aValue);
        if (isStrictlyNaN(parsed)) {
          return 0;
        }
        return parsed;
      },
      toICAL: function toICAL(aValue) {
        return String(aValue);
      }
    },
    "utc-offset": {
      toICAL: function toICAL(aValue) {
        if (aValue.length < 7) {
          // no seconds
          // -0500
          return aValue.slice(0, 3) + aValue.slice(4, 6);
        } else {
          // seconds
          // -050000
          return aValue.slice(0, 3) + aValue.slice(4, 6) + aValue.slice(7, 9);
        }
      },
      fromICAL: function fromICAL(aValue) {
        if (aValue.length < 6) {
          // no seconds
          // -05:00
          return aValue.slice(0, 3) + ':' + aValue.slice(3, 5);
        } else {
          // seconds
          // -05:00:00
          return aValue.slice(0, 3) + ':' + aValue.slice(3, 5) + ':' + aValue.slice(5, 7);
        }
      },
      decorate: function decorate(aValue) {
        return UtcOffset.fromString(aValue);
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      }
    }
  };
  var icalParams = {
    // Although the syntax is DQUOTE uri DQUOTE, I don't think we should
    // enforce anything aside from it being a valid content line.
    //
    // At least some params require - if multi values are used - DQUOTEs
    // for each of its values - e.g. delegated-from="uri1","uri2"
    // To indicate this, I introduced the new k/v pair
    // multiValueSeparateDQuote: true
    //
    // "ALTREP": { ... },

    // CN just wants a param-value
    // "CN": { ... }

    "cutype": {
      values: ["INDIVIDUAL", "GROUP", "RESOURCE", "ROOM", "UNKNOWN"],
      allowXName: true,
      allowIanaToken: true
    },
    "delegated-from": {
      valueType: "cal-address",
      multiValue: ",",
      multiValueSeparateDQuote: true
    },
    "delegated-to": {
      valueType: "cal-address",
      multiValue: ",",
      multiValueSeparateDQuote: true
    },
    // "DIR": { ... }, // See ALTREP
    "encoding": {
      values: ["8BIT", "BASE64"]
    },
    // "FMTTYPE": { ... }, // See ALTREP
    "fbtype": {
      values: ["FREE", "BUSY", "BUSY-UNAVAILABLE", "BUSY-TENTATIVE"],
      allowXName: true,
      allowIanaToken: true
    },
    // "LANGUAGE": { ... }, // See ALTREP
    "member": {
      valueType: "cal-address",
      multiValue: ",",
      multiValueSeparateDQuote: true
    },
    "partstat": {
      // TODO These values are actually different per-component
      values: ["NEEDS-ACTION", "ACCEPTED", "DECLINED", "TENTATIVE", "DELEGATED", "COMPLETED", "IN-PROCESS"],
      allowXName: true,
      allowIanaToken: true
    },
    "range": {
      values: ["THISANDFUTURE"]
    },
    "related": {
      values: ["START", "END"]
    },
    "reltype": {
      values: ["PARENT", "CHILD", "SIBLING"],
      allowXName: true,
      allowIanaToken: true
    },
    "role": {
      values: ["REQ-PARTICIPANT", "CHAIR", "OPT-PARTICIPANT", "NON-PARTICIPANT"],
      allowXName: true,
      allowIanaToken: true
    },
    "rsvp": {
      values: ["TRUE", "FALSE"]
    },
    "sent-by": {
      valueType: "cal-address"
    },
    "tzid": {
      matches: /^\//
    },
    "value": {
      // since the value here is a 'type' lowercase is used.
      values: ["binary", "boolean", "cal-address", "date", "date-time", "duration", "float", "integer", "period", "recur", "text", "time", "uri", "utc-offset"],
      allowXName: true,
      allowIanaToken: true
    }
  };

  // When adding a value here, be sure to add it to the parameter types!
  var icalValues = extend(commonValues, {
    text: createTextType(FROM_ICAL_NEWLINE, TO_ICAL_NEWLINE),
    uri: {
      // TODO
      /* ... */
    },
    "binary": {
      decorate: function decorate(aString) {
        return Binary.fromString(aString);
      },
      undecorate: function undecorate(aBinary) {
        return aBinary.toString();
      }
    },
    "cal-address": {
      // needs to be an uri
    },
    "date": {
      decorate: function decorate(aValue, aProp) {
        if (design.strict) {
          return Time.fromDateString(aValue, aProp);
        } else {
          return Time.fromString(aValue, aProp);
        }
      },
      /**
       * undecorates a time object.
       */
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      },
      fromICAL: function fromICAL(aValue) {
        // from: 20120901
        // to: 2012-09-01
        if (!design.strict && aValue.length >= 15) {
          // This is probably a date-time, e.g. 20120901T130000Z
          return icalValues["date-time"].fromICAL(aValue);
        } else {
          return aValue.slice(0, 4) + '-' + aValue.slice(4, 6) + '-' + aValue.slice(6, 8);
        }
      },
      toICAL: function toICAL(aValue) {
        // from: 2012-09-01
        // to: 20120901
        var len = aValue.length;
        if (len == 10) {
          return aValue.slice(0, 4) + aValue.slice(5, 7) + aValue.slice(8, 10);
        } else if (len >= 19) {
          return icalValues["date-time"].toICAL(aValue);
        } else {
          //TODO: serialize warning?
          return aValue;
        }
      }
    },
    "date-time": {
      fromICAL: function fromICAL(aValue) {
        // from: 20120901T130000
        // to: 2012-09-01T13:00:00
        if (!design.strict && aValue.length == 8) {
          // This is probably a date, e.g. 20120901
          return icalValues.date.fromICAL(aValue);
        } else {
          var result = aValue.slice(0, 4) + '-' + aValue.slice(4, 6) + '-' + aValue.slice(6, 8) + 'T' + aValue.slice(9, 11) + ':' + aValue.slice(11, 13) + ':' + aValue.slice(13, 15);
          if (aValue[15] && aValue[15] === 'Z') {
            result += 'Z';
          }
          return result;
        }
      },
      toICAL: function toICAL(aValue) {
        // from: 2012-09-01T13:00:00
        // to: 20120901T130000
        var len = aValue.length;
        if (len == 10 && !design.strict) {
          return icalValues.date.toICAL(aValue);
        } else if (len >= 19) {
          var result = aValue.slice(0, 4) + aValue.slice(5, 7) +
          // grab the (DDTHH) segment
          aValue.slice(8, 13) +
          // MM
          aValue.slice(14, 16) +
          // SS
          aValue.slice(17, 19);
          if (aValue[19] && aValue[19] === 'Z') {
            result += 'Z';
          }
          return result;
        } else {
          // TODO: error
          return aValue;
        }
      },
      decorate: function decorate(aValue, aProp) {
        if (design.strict) {
          return Time.fromDateTimeString(aValue, aProp);
        } else {
          return Time.fromString(aValue, aProp);
        }
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      }
    },
    duration: {
      decorate: function decorate(aValue) {
        return Duration.fromString(aValue);
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      }
    },
    period: {
      fromICAL: function fromICAL(string) {
        var parts = string.split('/');
        parts[0] = icalValues['date-time'].fromICAL(parts[0]);
        if (!Duration.isValueString(parts[1])) {
          parts[1] = icalValues['date-time'].fromICAL(parts[1]);
        }
        return parts;
      },
      toICAL: function toICAL(parts) {
        parts = parts.slice();
        if (!design.strict && parts[0].length == 10) {
          parts[0] = icalValues.date.toICAL(parts[0]);
        } else {
          parts[0] = icalValues['date-time'].toICAL(parts[0]);
        }
        if (!Duration.isValueString(parts[1])) {
          if (!design.strict && parts[1].length == 10) {
            parts[1] = icalValues.date.toICAL(parts[1]);
          } else {
            parts[1] = icalValues['date-time'].toICAL(parts[1]);
          }
        }
        return parts.join("/");
      },
      decorate: function decorate(aValue, aProp) {
        return Period.fromJSON(aValue, aProp, !design.strict);
      },
      undecorate: function undecorate(aValue) {
        return aValue.toJSON();
      }
    },
    recur: {
      fromICAL: function fromICAL(string) {
        return Recur._stringToData(string, true);
      },
      toICAL: function toICAL(data) {
        var str = "";
        for (var _i = 0, _Object$entries = Object.entries(data); _i < _Object$entries.length; _i++) {
          var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
            k = _Object$entries$_i[0],
            val = _Object$entries$_i[1];
          if (k == "until") {
            if (val.length > 10) {
              val = icalValues['date-time'].toICAL(val);
            } else {
              val = icalValues.date.toICAL(val);
            }
          } else if (k == "wkst") {
            if (typeof val === 'number') {
              val = Recur.numericDayToIcalDay(val);
            }
          } else if (Array.isArray(val)) {
            val = val.join(",");
          }
          str += k.toUpperCase() + "=" + val + ";";
        }
        return str.slice(0, Math.max(0, str.length - 1));
      },
      decorate: function decorate(aValue) {
        return Recur.fromData(aValue);
      },
      undecorate: function undecorate(aRecur) {
        return aRecur.toJSON();
      }
    },
    time: {
      fromICAL: function fromICAL(aValue) {
        // from: MMHHSS(Z)?
        // to: HH:MM:SS(Z)?
        if (aValue.length < 6) {
          // TODO: parser exception?
          return aValue;
        }

        // HH::MM::SSZ?
        var result = aValue.slice(0, 2) + ':' + aValue.slice(2, 4) + ':' + aValue.slice(4, 6);
        if (aValue[6] === 'Z') {
          result += 'Z';
        }
        return result;
      },
      toICAL: function toICAL(aValue) {
        // from: HH:MM:SS(Z)?
        // to: MMHHSS(Z)?
        if (aValue.length < 8) {
          //TODO: error
          return aValue;
        }
        var result = aValue.slice(0, 2) + aValue.slice(3, 5) + aValue.slice(6, 8);
        if (aValue[8] === 'Z') {
          result += 'Z';
        }
        return result;
      }
    }
  });
  var icalProperties = extend(commonProperties, {
    "action": DEFAULT_TYPE_TEXT,
    "attach": {
      defaultType: "uri"
    },
    "attendee": {
      defaultType: "cal-address"
    },
    "calscale": DEFAULT_TYPE_TEXT,
    "class": DEFAULT_TYPE_TEXT,
    "comment": DEFAULT_TYPE_TEXT,
    "completed": DEFAULT_TYPE_DATETIME,
    "contact": DEFAULT_TYPE_TEXT,
    "created": DEFAULT_TYPE_DATETIME,
    "description": DEFAULT_TYPE_TEXT,
    "dtend": DEFAULT_TYPE_DATETIME_DATE,
    "dtstamp": DEFAULT_TYPE_DATETIME,
    "dtstart": DEFAULT_TYPE_DATETIME_DATE,
    "due": DEFAULT_TYPE_DATETIME_DATE,
    "duration": {
      defaultType: "duration"
    },
    "exdate": {
      defaultType: "date-time",
      allowedTypes: ["date-time", "date"],
      multiValue: ','
    },
    "exrule": DEFAULT_TYPE_RECUR,
    "freebusy": {
      defaultType: "period",
      multiValue: ","
    },
    "geo": {
      defaultType: "float",
      structuredValue: ";"
    },
    "last-modified": DEFAULT_TYPE_DATETIME,
    "location": DEFAULT_TYPE_TEXT,
    "method": DEFAULT_TYPE_TEXT,
    "organizer": {
      defaultType: "cal-address"
    },
    "percent-complete": DEFAULT_TYPE_INTEGER,
    "priority": DEFAULT_TYPE_INTEGER,
    "prodid": DEFAULT_TYPE_TEXT,
    "related-to": DEFAULT_TYPE_TEXT,
    "repeat": DEFAULT_TYPE_INTEGER,
    "rdate": {
      defaultType: "date-time",
      allowedTypes: ["date-time", "date", "period"],
      multiValue: ',',
      detectType: function detectType(string) {
        if (string.indexOf('/') !== -1) {
          return 'period';
        }
        return string.indexOf('T') === -1 ? 'date' : 'date-time';
      }
    },
    "recurrence-id": DEFAULT_TYPE_DATETIME_DATE,
    "resources": DEFAULT_TYPE_TEXT_MULTI,
    "request-status": DEFAULT_TYPE_TEXT_STRUCTURED,
    "rrule": DEFAULT_TYPE_RECUR,
    "sequence": DEFAULT_TYPE_INTEGER,
    "status": DEFAULT_TYPE_TEXT,
    "summary": DEFAULT_TYPE_TEXT,
    "transp": DEFAULT_TYPE_TEXT,
    "trigger": {
      defaultType: "duration",
      allowedTypes: ["duration", "date-time"]
    },
    "tzoffsetfrom": DEFAULT_TYPE_UTCOFFSET,
    "tzoffsetto": DEFAULT_TYPE_UTCOFFSET,
    "tzurl": DEFAULT_TYPE_URI,
    "tzid": DEFAULT_TYPE_TEXT,
    "tzname": DEFAULT_TYPE_TEXT
  });

  // When adding a value here, be sure to add it to the parameter types!
  var vcardValues = extend(commonValues, {
    text: createTextType(FROM_VCARD_NEWLINE, TO_VCARD_NEWLINE),
    uri: createTextType(FROM_VCARD_NEWLINE, TO_VCARD_NEWLINE),
    date: {
      decorate: function decorate(aValue) {
        return VCardTime.fromDateAndOrTimeString(aValue, "date");
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      },
      fromICAL: function fromICAL(aValue) {
        if (aValue.length == 8) {
          return icalValues.date.fromICAL(aValue);
        } else if (aValue[0] == '-' && aValue.length == 6) {
          return aValue.slice(0, 4) + '-' + aValue.slice(4);
        } else {
          return aValue;
        }
      },
      toICAL: function toICAL(aValue) {
        if (aValue.length == 10) {
          return icalValues.date.toICAL(aValue);
        } else if (aValue[0] == '-' && aValue.length == 7) {
          return aValue.slice(0, 4) + aValue.slice(5);
        } else {
          return aValue;
        }
      }
    },
    time: {
      decorate: function decorate(aValue) {
        return VCardTime.fromDateAndOrTimeString("T" + aValue, "time");
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      },
      fromICAL: function fromICAL(aValue) {
        var splitzone = vcardValues.time._splitZone(aValue, true);
        var zone = splitzone[0],
          value = splitzone[1];

        //console.log("SPLIT: ",splitzone);

        if (value.length == 6) {
          value = value.slice(0, 2) + ':' + value.slice(2, 4) + ':' + value.slice(4, 6);
        } else if (value.length == 4 && value[0] != '-') {
          value = value.slice(0, 2) + ':' + value.slice(2, 4);
        } else if (value.length == 5) {
          value = value.slice(0, 3) + ':' + value.slice(3, 5);
        }
        if (zone.length == 5 && (zone[0] == '-' || zone[0] == '+')) {
          zone = zone.slice(0, 3) + ':' + zone.slice(3);
        }
        return value + zone;
      },
      toICAL: function toICAL(aValue) {
        var splitzone = vcardValues.time._splitZone(aValue);
        var zone = splitzone[0],
          value = splitzone[1];
        if (value.length == 8) {
          value = value.slice(0, 2) + value.slice(3, 5) + value.slice(6, 8);
        } else if (value.length == 5 && value[0] != '-') {
          value = value.slice(0, 2) + value.slice(3, 5);
        } else if (value.length == 6) {
          value = value.slice(0, 3) + value.slice(4, 6);
        }
        if (zone.length == 6 && (zone[0] == '-' || zone[0] == '+')) {
          zone = zone.slice(0, 3) + zone.slice(4);
        }
        return value + zone;
      },
      _splitZone: function _splitZone(aValue, isFromIcal) {
        var lastChar = aValue.length - 1;
        var signChar = aValue.length - (isFromIcal ? 5 : 6);
        var sign = aValue[signChar];
        var zone, value;
        if (aValue[lastChar] == 'Z') {
          zone = aValue[lastChar];
          value = aValue.slice(0, Math.max(0, lastChar));
        } else if (aValue.length > 6 && (sign == '-' || sign == '+')) {
          zone = aValue.slice(signChar);
          value = aValue.slice(0, Math.max(0, signChar));
        } else {
          zone = "";
          value = aValue;
        }
        return [zone, value];
      }
    },
    "date-time": {
      decorate: function decorate(aValue) {
        return VCardTime.fromDateAndOrTimeString(aValue, "date-time");
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      },
      fromICAL: function fromICAL(aValue) {
        return vcardValues['date-and-or-time'].fromICAL(aValue);
      },
      toICAL: function toICAL(aValue) {
        return vcardValues['date-and-or-time'].toICAL(aValue);
      }
    },
    "date-and-or-time": {
      decorate: function decorate(aValue) {
        return VCardTime.fromDateAndOrTimeString(aValue, "date-and-or-time");
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      },
      fromICAL: function fromICAL(aValue) {
        var parts = aValue.split('T');
        return (parts[0] ? vcardValues.date.fromICAL(parts[0]) : '') + (parts[1] ? 'T' + vcardValues.time.fromICAL(parts[1]) : '');
      },
      toICAL: function toICAL(aValue) {
        var parts = aValue.split('T');
        return vcardValues.date.toICAL(parts[0]) + (parts[1] ? 'T' + vcardValues.time.toICAL(parts[1]) : '');
      }
    },
    timestamp: icalValues['date-time'],
    "language-tag": {
      matches: /^[a-zA-Z0-9-]+$/ // Could go with a more strict regex here
    },
    "phone-number": {
      fromICAL: function fromICAL(aValue) {
        return Array.from(aValue).filter(function (c) {
          return c === '\\' ? undefined : c;
        }).join('');
      },
      toICAL: function toICAL(aValue) {
        return Array.from(aValue).map(function (c) {
          return c === ',' || c === ";" ? '\\' + c : c;
        }).join('');
      }
    }
  });
  var vcardParams = {
    "type": {
      valueType: "text",
      multiValue: ","
    },
    "value": {
      // since the value here is a 'type' lowercase is used.
      values: ["text", "uri", "date", "time", "date-time", "date-and-or-time", "timestamp", "boolean", "integer", "float", "utc-offset", "language-tag"],
      allowXName: true,
      allowIanaToken: true
    }
  };
  var vcardProperties = extend(commonProperties, {
    "adr": {
      defaultType: "text",
      structuredValue: ";",
      multiValue: ","
    },
    "anniversary": DEFAULT_TYPE_DATE_ANDOR_TIME,
    "bday": DEFAULT_TYPE_DATE_ANDOR_TIME,
    "caladruri": DEFAULT_TYPE_URI,
    "caluri": DEFAULT_TYPE_URI,
    "clientpidmap": DEFAULT_TYPE_TEXT_STRUCTURED,
    "email": DEFAULT_TYPE_TEXT,
    "fburl": DEFAULT_TYPE_URI,
    "fn": DEFAULT_TYPE_TEXT,
    "gender": DEFAULT_TYPE_TEXT_STRUCTURED,
    "geo": DEFAULT_TYPE_URI,
    "impp": DEFAULT_TYPE_URI,
    "key": DEFAULT_TYPE_URI,
    "kind": DEFAULT_TYPE_TEXT,
    "lang": {
      defaultType: "language-tag"
    },
    "logo": DEFAULT_TYPE_URI,
    "member": DEFAULT_TYPE_URI,
    "n": {
      defaultType: "text",
      structuredValue: ";",
      multiValue: ","
    },
    "nickname": DEFAULT_TYPE_TEXT_MULTI,
    "note": DEFAULT_TYPE_TEXT,
    "org": {
      defaultType: "text",
      structuredValue: ";"
    },
    "photo": DEFAULT_TYPE_URI,
    "related": DEFAULT_TYPE_URI,
    "rev": {
      defaultType: "timestamp"
    },
    "role": DEFAULT_TYPE_TEXT,
    "sound": DEFAULT_TYPE_URI,
    "source": DEFAULT_TYPE_URI,
    "tel": {
      defaultType: "uri",
      allowedTypes: ["uri", "text"]
    },
    "title": DEFAULT_TYPE_TEXT,
    "tz": {
      defaultType: "text",
      allowedTypes: ["text", "utc-offset", "uri"]
    },
    "xml": DEFAULT_TYPE_TEXT
  });
  var vcard3Values = extend(commonValues, {
    binary: icalValues.binary,
    date: vcardValues.date,
    "date-time": vcardValues["date-time"],
    "phone-number": vcardValues["phone-number"],
    uri: icalValues.uri,
    text: icalValues.text,
    time: icalValues.time,
    vcard: icalValues.text,
    "utc-offset": {
      toICAL: function toICAL(aValue) {
        return aValue.slice(0, 7);
      },
      fromICAL: function fromICAL(aValue) {
        return aValue.slice(0, 7);
      },
      decorate: function decorate(aValue) {
        return UtcOffset.fromString(aValue);
      },
      undecorate: function undecorate(aValue) {
        return aValue.toString();
      }
    }
  });
  var vcard3Params = {
    "type": {
      valueType: "text",
      multiValue: ","
    },
    "value": {
      // since the value here is a 'type' lowercase is used.
      values: ["text", "uri", "date", "date-time", "phone-number", "time", "boolean", "integer", "float", "utc-offset", "vcard", "binary"],
      allowXName: true,
      allowIanaToken: true
    }
  };
  var vcard3Properties = extend(commonProperties, {
    fn: DEFAULT_TYPE_TEXT,
    n: {
      defaultType: "text",
      structuredValue: ";",
      multiValue: ","
    },
    nickname: DEFAULT_TYPE_TEXT_MULTI,
    photo: {
      defaultType: "binary",
      allowedTypes: ["binary", "uri"]
    },
    bday: {
      defaultType: "date-time",
      allowedTypes: ["date-time", "date"],
      detectType: function detectType(string) {
        return string.indexOf('T') === -1 ? 'date' : 'date-time';
      }
    },
    adr: {
      defaultType: "text",
      structuredValue: ";",
      multiValue: ","
    },
    label: DEFAULT_TYPE_TEXT,
    tel: {
      defaultType: "phone-number"
    },
    email: DEFAULT_TYPE_TEXT,
    mailer: DEFAULT_TYPE_TEXT,
    tz: {
      defaultType: "utc-offset",
      allowedTypes: ["utc-offset", "text"]
    },
    geo: {
      defaultType: "float",
      structuredValue: ";"
    },
    title: DEFAULT_TYPE_TEXT,
    role: DEFAULT_TYPE_TEXT,
    logo: {
      defaultType: "binary",
      allowedTypes: ["binary", "uri"]
    },
    agent: {
      defaultType: "vcard",
      allowedTypes: ["vcard", "text", "uri"]
    },
    org: DEFAULT_TYPE_TEXT_STRUCTURED,
    note: DEFAULT_TYPE_TEXT_MULTI,
    prodid: DEFAULT_TYPE_TEXT,
    rev: {
      defaultType: "date-time",
      allowedTypes: ["date-time", "date"],
      detectType: function detectType(string) {
        return string.indexOf('T') === -1 ? 'date' : 'date-time';
      }
    },
    "sort-string": DEFAULT_TYPE_TEXT,
    sound: {
      defaultType: "binary",
      allowedTypes: ["binary", "uri"]
    },
    "class": DEFAULT_TYPE_TEXT,
    key: {
      defaultType: "binary",
      allowedTypes: ["binary", "text"]
    }
  });

  /**
   * iCalendar design set
   * @type {ICAL.design.designSet}
   */
  var icalSet = {
    value: icalValues,
    param: icalParams,
    property: icalProperties,
    propertyGroups: false
  };

  /**
   * vCard 4.0 design set
   * @type {ICAL.design.designSet}
   */
  var vcardSet = {
    value: vcardValues,
    param: vcardParams,
    property: vcardProperties,
    propertyGroups: true
  };

  /**
   * vCard 3.0 design set
   * @type {ICAL.design.designSet}
   */
  var vcard3Set = {
    value: vcard3Values,
    param: vcard3Params,
    property: vcard3Properties,
    propertyGroups: true
  };

  /**
   * The design data, used by the parser to determine types for properties and
   * other metadata needed to produce correct jCard/jCal data.
   *
   * @alias ICAL.design
   * @exports module:ICAL.design
   */
  var design = {
    /**
     * A designSet describes value, parameter and property data. It is used by
     * ther parser and stringifier in components and properties to determine they
     * should be represented.
     *
     * @typedef {Object} designSet
     * @memberOf ICAL.design
     * @property {Object} value       Definitions for value types, keys are type names
     * @property {Object} param       Definitions for params, keys are param names
     * @property {Object} property    Definitions for properties, keys are property names
     * @property {boolean} propertyGroups  If content lines may include a group name
     */

    /**
     * Can be set to false to make the parser more lenient.
     */
    strict: false,
    /**
     * The default set for new properties and components if none is specified.
     * @type {ICAL.design.designSet}
     */
    defaultSet: icalSet,
    /**
     * The default type for unknown properties
     * @type {String}
     */
    defaultType: 'unknown',
    /**
     * Holds the design set for known top-level components
     *
     * @type {Object}
     * @property {ICAL.design.designSet} vcard       vCard VCARD
     * @property {ICAL.design.designSet} vevent      iCalendar VEVENT
     * @property {ICAL.design.designSet} vtodo       iCalendar VTODO
     * @property {ICAL.design.designSet} vjournal    iCalendar VJOURNAL
     * @property {ICAL.design.designSet} valarm      iCalendar VALARM
     * @property {ICAL.design.designSet} vtimezone   iCalendar VTIMEZONE
     * @property {ICAL.design.designSet} daylight    iCalendar DAYLIGHT
     * @property {ICAL.design.designSet} standard    iCalendar STANDARD
     *
     * @example
     * let propertyName = 'fn';
     * let componentDesign = ICAL.design.components.vcard;
     * let propertyDetails = componentDesign.property[propertyName];
     * if (propertyDetails.defaultType == 'text') {
     *   // Yep, sure is...
     * }
     */
    components: {
      vcard: vcardSet,
      vcard3: vcard3Set,
      vevent: icalSet,
      vtodo: icalSet,
      vjournal: icalSet,
      valarm: icalSet,
      vtimezone: icalSet,
      daylight: icalSet,
      standard: icalSet
    },
    /**
     * The design set for iCalendar (rfc5545/rfc7265) components.
     * @type {ICAL.design.designSet}
     */
    icalendar: icalSet,
    /**
     * The design set for vCard (rfc6350/rfc7095) components.
     * @type {ICAL.design.designSet}
     */
    vcard: vcardSet,
    /**
     * The design set for vCard (rfc2425/rfc2426/rfc7095) components.
     * @type {ICAL.design.designSet}
     */
    vcard3: vcard3Set,
    /**
     * Gets the design set for the given component name.
     *
     * @param {String} componentName        The name of the component
     * @return {ICAL.design.designSet}      The design set for the component
     */
    getDesignSet: function getDesignSet(componentName) {
      var isInDesign = componentName && componentName in design.components;
      return isInDesign ? design.components[componentName] : design.defaultSet;
    }
  };

  var LINE_ENDING = '\r\n';
  var DEFAULT_VALUE_TYPE = 'unknown';
  var RFC6868_REPLACE_MAP = {
    '"': "^'",
    "\n": "^n",
    "^": "^^"
  };

  /**
   * Convert a full jCal/jCard array into a iCalendar/vCard string.
   *
   * @function ICAL.stringify
   * @variation function
   * @param {Array} jCal    The jCal/jCard document
   * @return {String}       The stringified iCalendar/vCard document
   */
  function stringify(jCal) {
    if (typeof jCal[0] == "string") {
      // This is a single component
      jCal = [jCal];
    }
    var i = 0;
    var len = jCal.length;
    var result = '';
    for (; i < len; i++) {
      result += stringify.component(jCal[i]) + LINE_ENDING;
    }
    return result;
  }

  /**
   * Converts an jCal component array into a ICAL string.
   * Recursive will resolve sub-components.
   *
   * Exact component/property order is not saved all
   * properties will come before subcomponents.
   *
   * @function ICAL.stringify.component
   * @param {Array} component
   *        jCal/jCard fragment of a component
   * @param {ICAL.design.designSet} designSet
   *        The design data to use for this component
   * @return {String}       The iCalendar/vCard string
   */
  stringify.component = function (component, designSet) {
    var name = component[0].toUpperCase();
    var result = 'BEGIN:' + name + LINE_ENDING;
    var props = component[1];
    var propIdx = 0;
    var propLen = props.length;
    var designSetName = component[0];
    // rfc6350 requires that in vCard 4.0 the first component is the VERSION
    // component with as value 4.0, note that 3.0 does not have this requirement.
    if (designSetName === 'vcard' && component[1].length > 0 && !(component[1][0][0] === "version" && component[1][0][3] === "4.0")) {
      designSetName = "vcard3";
    }
    designSet = designSet || design.getDesignSet(designSetName);
    for (; propIdx < propLen; propIdx++) {
      result += stringify.property(props[propIdx], designSet) + LINE_ENDING;
    }

    // Ignore subcomponents if none exist, e.g. in vCard.
    var comps = component[2] || [];
    var compIdx = 0;
    var compLen = comps.length;
    for (; compIdx < compLen; compIdx++) {
      result += stringify.component(comps[compIdx], designSet) + LINE_ENDING;
    }
    result += 'END:' + name;
    return result;
  };

  /**
   * Converts a single jCal/jCard property to a iCalendar/vCard string.
   *
   * @function ICAL.stringify.property
   * @param {Array} property
   *        jCal/jCard property array
   * @param {ICAL.design.designSet} designSet
   *        The design data to use for this property
   * @param {Boolean} noFold
   *        If true, the line is not folded
   * @return {String}       The iCalendar/vCard string
   */
  stringify.property = function (property, designSet, noFold) {
    var name = property[0].toUpperCase();
    var jsName = property[0];
    var params = property[1];
    if (!designSet) {
      designSet = design.defaultSet;
    }
    var groupName = params.group;
    var line;
    if (designSet.propertyGroups && groupName) {
      line = groupName.toUpperCase() + "." + name;
    } else {
      line = name;
    }
    var _loop = function _loop() {
      var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
        paramName = _Object$entries$_i[0],
        value = _Object$entries$_i[1];
      if (designSet.propertyGroups && paramName == 'group') {
        return 1; // continue
      }
      var paramDesign = designSet.param[paramName];
      var multiValue = paramDesign && paramDesign.multiValue;
      if (multiValue && Array.isArray(value)) {
        value = value.map(function (val) {
          val = stringify._rfc6868Unescape(val);
          val = stringify.paramPropertyValue(val, paramDesign.multiValueSeparateDQuote);
          return val;
        });
        value = stringify.multiValue(value, multiValue, "unknown", null, designSet);
      } else {
        value = stringify._rfc6868Unescape(value);
        value = stringify.paramPropertyValue(value);
      }
      line += ';' + paramName.toUpperCase() + '=' + value;
    };
    for (var _i = 0, _Object$entries = Object.entries(params); _i < _Object$entries.length; _i++) {
      if (_loop()) continue;
    }
    if (property.length === 3) {
      // If there are no values, we must assume a blank value
      return line + ':';
    }
    var valueType = property[2];
    var propDetails;
    var multiValue = false;
    var structuredValue = false;
    var isDefault = false;
    if (jsName in designSet.property) {
      propDetails = designSet.property[jsName];
      if ('multiValue' in propDetails) {
        multiValue = propDetails.multiValue;
      }
      if ('structuredValue' in propDetails && Array.isArray(property[3])) {
        structuredValue = propDetails.structuredValue;
      }
      if ('defaultType' in propDetails) {
        if (valueType === propDetails.defaultType) {
          isDefault = true;
        }
      } else {
        if (valueType === DEFAULT_VALUE_TYPE) {
          isDefault = true;
        }
      }
    } else {
      if (valueType === DEFAULT_VALUE_TYPE) {
        isDefault = true;
      }
    }

    // push the VALUE property if type is not the default
    // for the current property.
    if (!isDefault) {
      // value will never contain ;/:/, so we don't escape it here.
      line += ';VALUE=' + valueType.toUpperCase();
    }
    line += ':';
    if (multiValue && structuredValue) {
      line += stringify.multiValue(property[3], structuredValue, valueType, multiValue, designSet, structuredValue);
    } else if (multiValue) {
      line += stringify.multiValue(property.slice(3), multiValue, valueType, null, designSet, false);
    } else if (structuredValue) {
      line += stringify.multiValue(property[3], structuredValue, valueType, null, designSet, structuredValue);
    } else {
      line += stringify.value(property[3], valueType, designSet, false);
    }
    return noFold ? line : foldline(line);
  };

  /**
   * Handles escaping of property values that may contain:
   *
   *    COLON (:), SEMICOLON (;), or COMMA (,)
   *
   * If any of the above are present the result is wrapped
   * in double quotes.
   *
   * @function ICAL.stringify.paramPropertyValue
   * @param {String} value      Raw property value
   * @param {boolean} force     If value should be escaped even when unnecessary
   * @return {String}           Given or escaped value when needed
   */
  stringify.paramPropertyValue = function (value, force) {
    if (!force && unescapedIndexOf(value, ',') === -1 && unescapedIndexOf(value, ':') === -1 && unescapedIndexOf(value, ';') === -1) {
      return value;
    }
    return '"' + value + '"';
  };

  /**
   * Converts an array of ical values into a single
   * string based on a type and a delimiter value (like ",").
   *
   * @function ICAL.stringify.multiValue
   * @param {Array} values      List of values to convert
   * @param {String} delim      Used to join the values (",", ";", ":")
   * @param {String} type       Lowecase ical value type
   *        (like boolean, date-time, etc..)
   * @param {?String} innerMulti If set, each value will again be processed
   *        Used for structured values
   * @param {ICAL.design.designSet} designSet
   *        The design data to use for this property
   *
   * @return {String}           iCalendar/vCard string for value
   */
  stringify.multiValue = function (values, delim, type, innerMulti, designSet, structuredValue) {
    var result = '';
    var len = values.length;
    var i = 0;
    for (; i < len; i++) {
      if (innerMulti && Array.isArray(values[i])) {
        result += stringify.multiValue(values[i], innerMulti, type, null, designSet, structuredValue);
      } else {
        result += stringify.value(values[i], type, designSet, structuredValue);
      }
      if (i !== len - 1) {
        result += delim;
      }
    }
    return result;
  };

  /**
   * Processes a single ical value runs the associated "toICAL" method from the
   * design value type if available to convert the value.
   *
   * @function ICAL.stringify.value
   * @param {String|Number} value       A formatted value
   * @param {String} type               Lowercase iCalendar/vCard value type
   *  (like boolean, date-time, etc..)
   * @return {String}                   iCalendar/vCard value for single value
   */
  stringify.value = function (value, type, designSet, structuredValue) {
    if (type in designSet.value && 'toICAL' in designSet.value[type]) {
      return designSet.value[type].toICAL(value, structuredValue);
    }
    return value;
  };

  /**
   * Internal helper for rfc6868. Exposing this on ICAL.stringify so that
   * hackers can disable the rfc6868 parsing if the really need to.
   *
   * @param {String} val        The value to unescape
   * @return {String}           The escaped value
   */
  stringify._rfc6868Unescape = function (val) {
    return val.replace(/[\n^"]/g, function (x) {
      return RFC6868_REPLACE_MAP[x];
    });
  };

  /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   * Portions Copyright (C) Philipp Kewisch */

  var NAME_INDEX$1 = 0;
  var PROP_INDEX = 1;
  var TYPE_INDEX = 2;
  var VALUE_INDEX = 3;

  /**
   * Provides a layer on top of the raw jCal object for manipulating a single property, with its
   * parameters and value.
   *
   * @class
   * @alias ICAL.Property
   */
  var Property = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Property instance.
     *
     * It is important to note that mutations done in the wrapper directly mutate the jCal object used
     * to initialize.
     *
     * Can also be used to create new properties by passing the name of the property (as a String).
     *
     * @param {Array|String} jCal         Raw jCal representation OR the new name of the property
     * @param {ICAL.Component=} parent    Parent component
     */
    function Property(jCal, parent) {
      _classCallCheck(this, Property);
      this._parent = parent || null;
      if (typeof jCal === 'string') {
        // We are creating the property by name and need to detect the type
        this.jCal = [jCal, {}, design.defaultType];
        this.jCal[TYPE_INDEX] = this.getDefaultType();
      } else {
        this.jCal = jCal;
      }
      this._updateType();
    }

    /**
     * The value type for this property
     * @readonly
     * @type {String}
     */
    return _createClass(Property, [{
      key: "type",
      get: function get() {
        return this.jCal[TYPE_INDEX];
      }

      /**
       * The name of this property, in lowercase.
       * @readonly
       * @type {String}
       */
    }, {
      key: "name",
      get: function get() {
        return this.jCal[NAME_INDEX$1];
      }

      /**
       * The parent component for this property.
       * @type {ICAL.Component}
       */
    }, {
      key: "parent",
      get: function get() {
        return this._parent;
      },
      set: function set(p) {
        // Before setting the parent, check if the design set has changed. If it
        // has, we later need to update the type if it was unknown before.
        var designSetChanged = !this._parent || p && p._designSet != this._parent._designSet;
        this._parent = p;
        if (this.type == design.defaultType && designSetChanged) {
          this.jCal[TYPE_INDEX] = this.getDefaultType();
          this._updateType();
        }
      }

      /**
       * The design set for this property, e.g. icalendar vs vcard
       *
       * @type {ICAL.design.designSet}
       * @private
       */
    }, {
      key: "_designSet",
      get: function get() {
        return this.parent ? this.parent._designSet : design.defaultSet;
      }

      /**
       * Updates the type metadata from the current jCal type and design set.
       *
       * @private
       */
    }, {
      key: "_updateType",
      value: function _updateType() {
        var designSet = this._designSet;
        if (this.type in designSet.value) {
          if ('decorate' in designSet.value[this.type]) {
            this.isDecorated = true;
          } else {
            this.isDecorated = false;
          }
          if (this.name in designSet.property) {
            this.isMultiValue = 'multiValue' in designSet.property[this.name];
            this.isStructuredValue = 'structuredValue' in designSet.property[this.name];
          }
        }
      }

      /**
       * Hydrate a single value. The act of hydrating means turning the raw jCal
       * value into a potentially wrapped object, for example {@link ICAL.Time}.
       *
       * @private
       * @param {Number} index        The index of the value to hydrate
       * @return {Object}             The decorated value.
       */
    }, {
      key: "_hydrateValue",
      value: function _hydrateValue(index) {
        if (this._values && this._values[index]) {
          return this._values[index];
        }

        // for the case where there is no value.
        if (this.jCal.length <= VALUE_INDEX + index) {
          return null;
        }
        if (this.isDecorated) {
          if (!this._values) {
            this._values = [];
          }
          return this._values[index] = this._decorate(this.jCal[VALUE_INDEX + index]);
        } else {
          return this.jCal[VALUE_INDEX + index];
        }
      }

      /**
       * Decorate a single value, returning its wrapped object. This is used by
       * the hydrate function to actually wrap the value.
       *
       * @private
       * @param {?} value         The value to decorate
       * @return {Object}         The decorated value
       */
    }, {
      key: "_decorate",
      value: function _decorate(value) {
        return this._designSet.value[this.type].decorate(value, this);
      }

      /**
       * Undecorate a single value, returning its raw jCal data.
       *
       * @private
       * @param {Object} value         The value to undecorate
       * @return {?}                   The undecorated value
       */
    }, {
      key: "_undecorate",
      value: function _undecorate(value) {
        return this._designSet.value[this.type].undecorate(value, this);
      }

      /**
       * Sets the value at the given index while also hydrating it. The passed
       * value can either be a decorated or undecorated value.
       *
       * @private
       * @param {?} value             The value to set
       * @param {Number} index        The index to set it at
       */
    }, {
      key: "_setDecoratedValue",
      value: function _setDecoratedValue(value, index) {
        if (!this._values) {
          this._values = [];
        }
        if (_typeof(value) === 'object' && 'icaltype' in value) {
          // decorated value
          this.jCal[VALUE_INDEX + index] = this._undecorate(value);
          this._values[index] = value;
        } else {
          // undecorated value
          this.jCal[VALUE_INDEX + index] = value;
          this._values[index] = this._decorate(value);
        }
      }

      /**
       * Gets a parameter on the property.
       *
       * @param {String}        name   Parameter name (lowercase)
       * @return {Array|String}        Parameter value
       */
    }, {
      key: "getParameter",
      value: function getParameter(name) {
        if (name in this.jCal[PROP_INDEX]) {
          return this.jCal[PROP_INDEX][name];
        } else {
          return undefined;
        }
      }

      /**
       * Gets first parameter on the property.
       *
       * @param {String}        name   Parameter name (lowercase)
       * @return {String}        Parameter value
       */
    }, {
      key: "getFirstParameter",
      value: function getFirstParameter(name) {
        var parameters = this.getParameter(name);
        if (Array.isArray(parameters)) {
          return parameters[0];
        }
        return parameters;
      }

      /**
       * Sets a parameter on the property.
       *
       * @param {String}       name     The parameter name
       * @param {Array|String} value    The parameter value
       */
    }, {
      key: "setParameter",
      value: function setParameter(name, value) {
        var lcname = name.toLowerCase();
        if (typeof value === "string" && lcname in this._designSet.param && 'multiValue' in this._designSet.param[lcname]) {
          value = [value];
        }
        this.jCal[PROP_INDEX][name] = value;
      }

      /**
       * Removes a parameter
       *
       * @param {String} name     The parameter name
       */
    }, {
      key: "removeParameter",
      value: function removeParameter(name) {
        delete this.jCal[PROP_INDEX][name];
      }

      /**
       * Get the default type based on this property's name.
       *
       * @return {String}     The default type for this property
       */
    }, {
      key: "getDefaultType",
      value: function getDefaultType() {
        var name = this.jCal[NAME_INDEX$1];
        var designSet = this._designSet;
        if (name in designSet.property) {
          var details = designSet.property[name];
          if ('defaultType' in details) {
            return details.defaultType;
          }
        }
        return design.defaultType;
      }

      /**
       * Sets type of property and clears out any existing values of the current
       * type.
       *
       * @param {String} type     New iCAL type (see design.*.values)
       */
    }, {
      key: "resetType",
      value: function resetType(type) {
        this.removeAllValues();
        this.jCal[TYPE_INDEX] = type;
        this._updateType();
      }

      /**
       * Finds the first property value.
       *
       * @return {String}         First property value
       */
    }, {
      key: "getFirstValue",
      value: function getFirstValue() {
        return this._hydrateValue(0);
      }

      /**
       * Gets all values on the property.
       *
       * NOTE: this creates an array during each call.
       *
       * @return {Array}          List of values
       */
    }, {
      key: "getValues",
      value: function getValues() {
        var len = this.jCal.length - VALUE_INDEX;
        if (len < 1) {
          // it is possible for a property to have no value.
          return [];
        }
        var i = 0;
        var result = [];
        for (; i < len; i++) {
          result[i] = this._hydrateValue(i);
        }
        return result;
      }

      /**
       * Removes all values from this property
       */
    }, {
      key: "removeAllValues",
      value: function removeAllValues() {
        if (this._values) {
          this._values.length = 0;
        }
        this.jCal.length = 3;
      }

      /**
       * Sets the values of the property.  Will overwrite the existing values.
       * This can only be used for multi-value properties.
       *
       * @param {Array} values    An array of values
       */
    }, {
      key: "setValues",
      value: function setValues(values) {
        if (!this.isMultiValue) {
          throw new Error(this.name + ': does not not support mulitValue.\n' + 'override isMultiValue');
        }
        var len = values.length;
        var i = 0;
        this.removeAllValues();
        if (len > 0 && _typeof(values[0]) === 'object' && 'icaltype' in values[0]) {
          this.resetType(values[0].icaltype);
        }
        if (this.isDecorated) {
          for (; i < len; i++) {
            this._setDecoratedValue(values[i], i);
          }
        } else {
          for (; i < len; i++) {
            this.jCal[VALUE_INDEX + i] = values[i];
          }
        }
      }

      /**
       * Sets the current value of the property. If this is a multi-value
       * property, all other values will be removed.
       *
       * @param {String|Object} value     New property value.
       */
    }, {
      key: "setValue",
      value: function setValue(value) {
        this.removeAllValues();
        if (_typeof(value) === 'object' && 'icaltype' in value) {
          this.resetType(value.icaltype);
        }
        if (this.isDecorated) {
          this._setDecoratedValue(value, 0);
        } else {
          this.jCal[VALUE_INDEX] = value;
        }
      }

      /**
       * Returns the Object representation of this component. The returned object
       * is a live jCal object and should be cloned if modified.
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        return this.jCal;
      }

      /**
       * The string representation of this component.
       * @return {String}
       */
    }, {
      key: "toICALString",
      value: function toICALString() {
        return stringify.property(this.jCal, this._designSet, true);
      }
    }], [{
      key: "fromString",
      value:
      /**
       * Create an {@link ICAL.Property} by parsing the passed iCalendar string.
       *
       * @param {String} str                        The iCalendar string to parse
       * @param {ICAL.design.designSet=} designSet  The design data to use for this property
       * @return {ICAL.Property}                    The created iCalendar property
       */
      function fromString(str, designSet) {
        return new Property(parse.property(str, designSet));
      }
    }]);
  }();

  var NAME_INDEX = 0;
  var PROPERTY_INDEX = 1;
  var COMPONENT_INDEX = 2;

  /**
   * Wraps a jCal component, adding convenience methods to add, remove and update subcomponents and
   * properties.
   *
   * @class
   * @alias ICAL.Component
   */
  var Component = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Component instance.
     *
     * @param {Array|String} jCal         Raw jCal component data OR name of new
     *                                      component
     * @param {ICAL.Component} parent     Parent component to associate
     */
    function Component(jCal, parent) {
      _classCallCheck(this, Component);
      /**
       * Hydrated properties are inserted into the _properties array at the same
       * position as in the jCal array, so it is possible that the array contains
       * undefined values for unhydrdated properties. To avoid iterating the
       * array when checking if all properties have been hydrated, we save the
       * count here.
       *
       * @type {Number}
       * @private
       */
      _defineProperty(this, "_hydratedPropertyCount", 0);
      /**
       * The same count as for _hydratedPropertyCount, but for subcomponents
       *
       * @type {Number}
       * @private
       */
      _defineProperty(this, "_hydratedComponentCount", 0);
      /**
       * A cache of hydrated time zone objects which may be used by consumers, keyed
       * by time zone ID.
       *
       * @type {Map}
       * @private
       */
      _defineProperty(this, "_timezoneCache", null);
      if (typeof jCal === 'string') {
        // jCal spec (name, properties, components)
        jCal = [jCal, [], []];
      }

      // mostly for legacy reasons.
      this.jCal = jCal;
      this.parent = parent || null;
      if (!this.parent && this.name === 'vcalendar') {
        this._timezoneCache = new Map();
      }
    }
    return _createClass(Component, [{
      key: "name",
      get:
      /**
       * The name of this component
       * @readonly
       */
      function get() {
        return this.jCal[NAME_INDEX];
      }

      /**
       * The design set for this component, e.g. icalendar vs vcard
       *
       * @type {ICAL.design.designSet}
       * @private
       */
    }, {
      key: "_designSet",
      get: function get() {
        var parentDesign = this.parent && this.parent._designSet;
        return parentDesign || design.getDesignSet(this.name);
      }
    }, {
      key: "_hydrateComponent",
      value: function _hydrateComponent(index) {
        if (!this._components) {
          this._components = [];
          this._hydratedComponentCount = 0;
        }
        if (this._components[index]) {
          return this._components[index];
        }
        var comp = new Component(this.jCal[COMPONENT_INDEX][index], this);
        this._hydratedComponentCount++;
        return this._components[index] = comp;
      }
    }, {
      key: "_hydrateProperty",
      value: function _hydrateProperty(index) {
        if (!this._properties) {
          this._properties = [];
          this._hydratedPropertyCount = 0;
        }
        if (this._properties[index]) {
          return this._properties[index];
        }
        var prop = new Property(this.jCal[PROPERTY_INDEX][index], this);
        this._hydratedPropertyCount++;
        return this._properties[index] = prop;
      }

      /**
       * Finds first sub component, optionally filtered by name.
       *
       * @param {String=} name        Optional name to filter by
       * @return {?ICAL.Component}     The found subcomponent
       */
    }, {
      key: "getFirstSubcomponent",
      value: function getFirstSubcomponent(name) {
        if (name) {
          var i = 0;
          var comps = this.jCal[COMPONENT_INDEX];
          var len = comps.length;
          for (; i < len; i++) {
            if (comps[i][NAME_INDEX] === name) {
              var result = this._hydrateComponent(i);
              return result;
            }
          }
        } else {
          if (this.jCal[COMPONENT_INDEX].length) {
            return this._hydrateComponent(0);
          }
        }

        // ensure we return a value (strict mode)
        return null;
      }

      /**
       * Finds all sub components, optionally filtering by name.
       *
       * @param {String=} name            Optional name to filter by
       * @return {ICAL.Component[]}       The found sub components
       */
    }, {
      key: "getAllSubcomponents",
      value: function getAllSubcomponents(name) {
        var jCalLen = this.jCal[COMPONENT_INDEX].length;
        var i = 0;
        if (name) {
          var comps = this.jCal[COMPONENT_INDEX];
          var result = [];
          for (; i < jCalLen; i++) {
            if (name === comps[i][NAME_INDEX]) {
              result.push(this._hydrateComponent(i));
            }
          }
          return result;
        } else {
          if (!this._components || this._hydratedComponentCount !== jCalLen) {
            for (; i < jCalLen; i++) {
              this._hydrateComponent(i);
            }
          }
          return this._components || [];
        }
      }

      /**
       * Returns true when a named property exists.
       *
       * @param {String} name     The property name
       * @return {Boolean}        True, when property is found
       */
    }, {
      key: "hasProperty",
      value: function hasProperty(name) {
        var props = this.jCal[PROPERTY_INDEX];
        var len = props.length;
        var i = 0;
        for (; i < len; i++) {
          // 0 is property name
          if (props[i][NAME_INDEX] === name) {
            return true;
          }
        }
        return false;
      }

      /**
       * Finds the first property, optionally with the given name.
       *
       * @param {String=} name        Lowercase property name
       * @return {?ICAL.Property}     The found property
       */
    }, {
      key: "getFirstProperty",
      value: function getFirstProperty(name) {
        if (name) {
          var i = 0;
          var props = this.jCal[PROPERTY_INDEX];
          var len = props.length;
          for (; i < len; i++) {
            if (props[i][NAME_INDEX] === name) {
              var result = this._hydrateProperty(i);
              return result;
            }
          }
        } else {
          if (this.jCal[PROPERTY_INDEX].length) {
            return this._hydrateProperty(0);
          }
        }
        return null;
      }

      /**
       * Returns first property's value, if available.
       *
       * @param {String=} name    Lowercase property name
       * @return {?String}        The found property value.
       */
    }, {
      key: "getFirstPropertyValue",
      value: function getFirstPropertyValue(name) {
        var prop = this.getFirstProperty(name);
        if (prop) {
          return prop.getFirstValue();
        }
        return null;
      }

      /**
       * Get all properties in the component, optionally filtered by name.
       *
       * @param {String=} name        Lowercase property name
       * @return {ICAL.Property[]}    List of properties
       */
    }, {
      key: "getAllProperties",
      value: function getAllProperties(name) {
        var jCalLen = this.jCal[PROPERTY_INDEX].length;
        var i = 0;
        if (name) {
          var props = this.jCal[PROPERTY_INDEX];
          var result = [];
          for (; i < jCalLen; i++) {
            if (name === props[i][NAME_INDEX]) {
              result.push(this._hydrateProperty(i));
            }
          }
          return result;
        } else {
          if (!this._properties || this._hydratedPropertyCount !== jCalLen) {
            for (; i < jCalLen; i++) {
              this._hydrateProperty(i);
            }
          }
          return this._properties || [];
        }
      }
    }, {
      key: "_removeObjectByIndex",
      value: function _removeObjectByIndex(jCalIndex, cache, index) {
        cache = cache || [];
        // remove cached version
        if (cache[index]) {
          var obj = cache[index];
          if ("parent" in obj) {
            obj.parent = null;
          }
        }
        cache.splice(index, 1);

        // remove it from the jCal
        this.jCal[jCalIndex].splice(index, 1);
      }
    }, {
      key: "_removeObject",
      value: function _removeObject(jCalIndex, cache, nameOrObject) {
        var i = 0;
        var objects = this.jCal[jCalIndex];
        var len = objects.length;
        var cached = this[cache];
        if (typeof nameOrObject === 'string') {
          for (; i < len; i++) {
            if (objects[i][NAME_INDEX] === nameOrObject) {
              this._removeObjectByIndex(jCalIndex, cached, i);
              return true;
            }
          }
        } else if (cached) {
          for (; i < len; i++) {
            if (cached[i] && cached[i] === nameOrObject) {
              this._removeObjectByIndex(jCalIndex, cached, i);
              return true;
            }
          }
        }
        return false;
      }
    }, {
      key: "_removeAllObjects",
      value: function _removeAllObjects(jCalIndex, cache, name) {
        var cached = this[cache];

        // Unfortunately we have to run through all children to reset their
        // parent property.
        var objects = this.jCal[jCalIndex];
        var i = objects.length - 1;

        // descending search required because splice
        // is used and will effect the indices.
        for (; i >= 0; i--) {
          if (!name || objects[i][NAME_INDEX] === name) {
            this._removeObjectByIndex(jCalIndex, cached, i);
          }
        }
      }

      /**
       * Adds a single sub component.
       *
       * @param {ICAL.Component} component        The component to add
       * @return {ICAL.Component}                 The passed in component
       */
    }, {
      key: "addSubcomponent",
      value: function addSubcomponent(component) {
        if (!this._components) {
          this._components = [];
          this._hydratedComponentCount = 0;
        }
        if (component.parent) {
          component.parent.removeSubcomponent(component);
        }
        var idx = this.jCal[COMPONENT_INDEX].push(component.jCal);
        this._components[idx - 1] = component;
        this._hydratedComponentCount++;
        component.parent = this;
        return component;
      }

      /**
       * Removes a single component by name or the instance of a specific
       * component.
       *
       * @param {ICAL.Component|String} nameOrComp    Name of component, or component
       * @return {Boolean}                            True when comp is removed
       */
    }, {
      key: "removeSubcomponent",
      value: function removeSubcomponent(nameOrComp) {
        var removed = this._removeObject(COMPONENT_INDEX, '_components', nameOrComp);
        if (removed) {
          this._hydratedComponentCount--;
        }
        return removed;
      }

      /**
       * Removes all components or (if given) all components by a particular
       * name.
       *
       * @param {String=} name            Lowercase component name
       */
    }, {
      key: "removeAllSubcomponents",
      value: function removeAllSubcomponents(name) {
        var removed = this._removeAllObjects(COMPONENT_INDEX, '_components', name);
        this._hydratedComponentCount = 0;
        return removed;
      }

      /**
       * Adds an {@link ICAL.Property} to the component.
       *
       * @param {ICAL.Property} property      The property to add
       * @return {ICAL.Property}              The passed in property
       */
    }, {
      key: "addProperty",
      value: function addProperty(property) {
        if (!(property instanceof Property)) {
          throw new TypeError('must be instance of ICAL.Property');
        }
        if (!this._properties) {
          this._properties = [];
          this._hydratedPropertyCount = 0;
        }
        if (property.parent) {
          property.parent.removeProperty(property);
        }
        var idx = this.jCal[PROPERTY_INDEX].push(property.jCal);
        this._properties[idx - 1] = property;
        this._hydratedPropertyCount++;
        property.parent = this;
        return property;
      }

      /**
       * Helper method to add a property with a value to the component.
       *
       * @param {String}               name         Property name to add
       * @param {String|Number|Object} value        Property value
       * @return {ICAL.Property}                    The created property
       */
    }, {
      key: "addPropertyWithValue",
      value: function addPropertyWithValue(name, value) {
        var prop = new Property(name);
        prop.setValue(value);
        this.addProperty(prop);
        return prop;
      }

      /**
       * Helper method that will update or create a property of the given name
       * and sets its value. If multiple properties with the given name exist,
       * only the first is updated.
       *
       * @param {String}               name         Property name to update
       * @param {String|Number|Object} value        Property value
       * @return {ICAL.Property}                    The created property
       */
    }, {
      key: "updatePropertyWithValue",
      value: function updatePropertyWithValue(name, value) {
        var prop = this.getFirstProperty(name);
        if (prop) {
          prop.setValue(value);
        } else {
          prop = this.addPropertyWithValue(name, value);
        }
        return prop;
      }

      /**
       * Removes a single property by name or the instance of the specific
       * property.
       *
       * @param {String|ICAL.Property} nameOrProp     Property name or instance to remove
       * @return {Boolean}                            True, when deleted
       */
    }, {
      key: "removeProperty",
      value: function removeProperty(nameOrProp) {
        var removed = this._removeObject(PROPERTY_INDEX, '_properties', nameOrProp);
        if (removed) {
          this._hydratedPropertyCount--;
        }
        return removed;
      }

      /**
       * Removes all properties associated with this component, optionally
       * filtered by name.
       *
       * @param {String=} name        Lowercase property name
       * @return {Boolean}            True, when deleted
       */
    }, {
      key: "removeAllProperties",
      value: function removeAllProperties(name) {
        var removed = this._removeAllObjects(PROPERTY_INDEX, '_properties', name);
        this._hydratedPropertyCount = 0;
        return removed;
      }

      /**
       * Returns the Object representation of this component. The returned object
       * is a live jCal object and should be cloned if modified.
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        return this.jCal;
      }

      /**
       * The string representation of this component.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        return stringify.component(this.jCal, this._designSet);
      }

      /**
       * Retrieve a time zone definition from the component tree, if any is present.
       * If the tree contains no time zone definitions or the TZID cannot be
       * matched, returns null.
       *
       * @param {String} tzid     The ID of the time zone to retrieve
       * @return {ICAL.Timezone}  The time zone corresponding to the ID, or null
       */
    }, {
      key: "getTimeZoneByID",
      value: function getTimeZoneByID(tzid) {
        // VTIMEZONE components can only appear as a child of the VCALENDAR
        // component; walk the tree if we're not the root.
        if (this.parent) {
          return this.parent.getTimeZoneByID(tzid);
        }

        // If there is no time zone cache, we are probably parsing an incomplete
        // file and will have no time zone definitions.
        if (!this._timezoneCache) {
          return null;
        }
        if (this._timezoneCache.has(tzid)) {
          return this._timezoneCache.get(tzid);
        }

        // If the time zone is not already cached, hydrate it from the
        // subcomponents.
        var zones = this.getAllSubcomponents('vtimezone');
        var _iterator = _createForOfIteratorHelper(zones),
          _step;
        try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
            var zone = _step.value;
            if (zone.getFirstProperty('tzid').getFirstValue() === tzid) {
              var hydratedZone = new Timezone({
                component: zone,
                tzid: tzid
              });
              this._timezoneCache.set(tzid, hydratedZone);
              return hydratedZone;
            }
          }

          // Per the standard, we should always have a time zone defined in a file
          // for any referenced TZID, but don't blow up if the file is invalid.
        } catch (err) {
          _iterator.e(err);
        } finally {
          _iterator.f();
        }
        return null;
      }
    }], [{
      key: "fromString",
      value:
      /**
       * Create an {@link ICAL.Component} by parsing the passed iCalendar string.
       *
       * @param {String} str        The iCalendar string to parse
       */
      function fromString(str) {
        return new Component(parse.component(str));
      }
    }]);
  }();

  /**
   * Primary class for expanding recurring rules.  Can take multiple rrules, rdates, exdate(s) and
   * iterate (in order) over each next occurrence.
   *
   * Once initialized this class can also be serialized saved and continue iteration from the last
   * point.
   *
   * NOTE: it is intended that this class is to be used with {@link ICAL.Event} which handles recurrence
   * exceptions.
   *
   * @example
   * // assuming event is a parsed ical component
   * var event;
   *
   * var expand = new ICAL.RecurExpansion({
   *   component: event,
   *   dtstart: event.getFirstPropertyValue('dtstart')
   * });
   *
   * // remember there are infinite rules so it is a good idea to limit the scope of the iterations
   * // then resume later on.
   *
   * // next is always an ICAL.Time or null
   * var next;
   *
   * while (someCondition && (next = expand.next())) {
   *   // do something with next
   * }
   *
   * // save instance for later
   * var json = JSON.stringify(expand);
   *
   * //...
   *
   * // NOTE: if the component's properties have changed you will need to rebuild the class and start
   * // over. This only works when the component's recurrence info is the same.
   * var expand = new ICAL.RecurExpansion(JSON.parse(json));
   *
   * @class
   * @alias ICAL.RecurExpansion
   */
  var RecurExpansion = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.RecurExpansion instance.
     *
     * The options object can be filled with the specified initial values. It can also contain
     * additional members, as a result of serializing a previous expansion state, as shown in the
     * example.
     *
     * @class
     * @alias ICAL.RecurExpansion
     * @param {Object} options
     *        Recurrence expansion options
     * @param {ICAL.Time} options.dtstart
     *        Start time of the event
     * @param {ICAL.Component=} options.component
     *        Component for expansion, required if not resuming.
     */
    function RecurExpansion(options) {
      _classCallCheck(this, RecurExpansion);
      /**
       * True when iteration is fully completed.
       * @type {Boolean}
       */
      _defineProperty(this, "complete", false);
      /**
       * Array of rrule iterators.
       *
       * @type {ICAL.RecurIterator[]}
       * @private
       */
      _defineProperty(this, "ruleIterators", null);
      /**
       * Array of rdate instances.
       *
       * @type {ICAL.Time[]}
       * @private
       */
      _defineProperty(this, "ruleDates", null);
      /**
       * Array of exdate instances.
       *
       * @type {ICAL.Time[]}
       * @private
       */
      _defineProperty(this, "exDates", null);
      /**
       * Current position in ruleDates array.
       * @type {Number}
       * @private
       */
      _defineProperty(this, "ruleDateInc", 0);
      /**
       * Current position in exDates array
       * @type {Number}
       * @private
       */
      _defineProperty(this, "exDateInc", 0);
      /**
       * Current negative date.
       *
       * @type {ICAL.Time}
       * @private
       */
      _defineProperty(this, "exDate", null);
      /**
       * Current additional date.
       *
       * @type {ICAL.Time}
       * @private
       */
      _defineProperty(this, "ruleDate", null);
      /**
       * Start date of recurring rules.
       *
       * @type {ICAL.Time}
       */
      _defineProperty(this, "dtstart", null);
      /**
       * Last expanded time
       *
       * @type {ICAL.Time}
       */
      _defineProperty(this, "last", null);
      this.ruleDates = [];
      this.exDates = [];
      this.fromData(options);
    }
    return _createClass(RecurExpansion, [{
      key: "fromData",
      value:
      /**
       * Initialize the recurrence expansion from the data object. The options
       * object may also contain additional members, see the
       * {@link ICAL.RecurExpansion constructor} for more details.
       *
       * @param {Object} options
       *        Recurrence expansion options
       * @param {ICAL.Time} options.dtstart
       *        Start time of the event
       * @param {ICAL.Component=} options.component
       *        Component for expansion, required if not resuming.
       */
      function fromData(options) {
        var start = formatClassType(options.dtstart, Time);
        if (!start) {
          throw new Error('.dtstart (ICAL.Time) must be given');
        } else {
          this.dtstart = start;
        }
        if (options.component) {
          this._init(options.component);
        } else {
          this.last = formatClassType(options.last, Time) || start.clone();
          if (!options.ruleIterators) {
            throw new Error('.ruleIterators or .component must be given');
          }
          this.ruleIterators = options.ruleIterators.map(function (item) {
            return formatClassType(item, RecurIterator);
          });
          this.ruleDateInc = options.ruleDateInc;
          this.exDateInc = options.exDateInc;
          if (options.ruleDates) {
            this.ruleDates = options.ruleDates.map(function (item) {
              return formatClassType(item, Time);
            });
            this.ruleDate = this.ruleDates[this.ruleDateInc];
          }
          if (options.exDates) {
            this.exDates = options.exDates.map(function (item) {
              return formatClassType(item, Time);
            });
            this.exDate = this.exDates[this.exDateInc];
          }
          if (typeof options.complete !== 'undefined') {
            this.complete = options.complete;
          }
        }
      }

      /**
       * Retrieve the next occurrence in the series.
       * @return {ICAL.Time}
       */
    }, {
      key: "next",
      value: function next() {
        var iter;
        var next;
        var compare;
        var maxTries = 500;
        var currentTry = 0;
        while (true) {
          // eslint-disable-line no-constant-condition
          if (currentTry++ > maxTries) {
            throw new Error('max tries have occurred, rule may be impossible to fulfill.');
          }
          next = this.ruleDate;
          iter = this._nextRecurrenceIter(this.last);

          // no more matches
          // because we increment the rule day or rule
          // _after_ we choose a value this should be
          // the only spot where we need to worry about the
          // end of events.
          if (!next && !iter) {
            // there are no more iterators or rdates
            this.complete = true;
            break;
          }

          // no next rule day or recurrence rule is first.
          if (!next || iter && next.compare(iter.last) > 0) {
            // must be cloned, recur will reuse the time element.
            next = iter.last.clone();
            // move to next so we can continue
            iter.next();
          }

          // if the ruleDate is still next increment it.
          if (this.ruleDate === next) {
            this._nextRuleDay();
          }
          this.last = next;

          // check the negative rules
          if (this.exDate) {
            compare = this.exDate.compare(this.last);
            if (compare < 0) {
              this._nextExDay();
            }

            // if the current rule is excluded skip it.
            if (compare === 0) {
              this._nextExDay();
              continue;
            }
          }

          //XXX: The spec states that after we resolve the final
          //     list of dates we execute exdate this seems somewhat counter
          //     intuitive to what I have seen most servers do so for now
          //     I exclude based on the original date not the one that may
          //     have been modified by the exception.
          return this.last;
        }
      }

      /**
       * Converts object into a serialize-able format. This format can be passed
       * back into the expansion to resume iteration.
       * @return {Object}
       */
    }, {
      key: "toJSON",
      value: function toJSON() {
        function toJSON(item) {
          return item.toJSON();
        }
        var result = Object.create(null);
        result.ruleIterators = this.ruleIterators.map(toJSON);
        if (this.ruleDates) {
          result.ruleDates = this.ruleDates.map(toJSON);
        }
        if (this.exDates) {
          result.exDates = this.exDates.map(toJSON);
        }
        result.ruleDateInc = this.ruleDateInc;
        result.exDateInc = this.exDateInc;
        result.last = this.last.toJSON();
        result.dtstart = this.dtstart.toJSON();
        result.complete = this.complete;
        return result;
      }

      /**
       * Extract all dates from the properties in the given component. The
       * properties will be filtered by the property name.
       *
       * @private
       * @param {ICAL.Component} component        The component to search in
       * @param {String} propertyName             The property name to search for
       * @return {ICAL.Time[]}                    The extracted dates.
       */
    }, {
      key: "_extractDates",
      value: function _extractDates(component, propertyName) {
        var result = [];
        var props = component.getAllProperties(propertyName);
        for (var i = 0, len = props.length; i < len; i++) {
          var _iterator = _createForOfIteratorHelper(props[i].getValues()),
            _step;
          try {
            for (_iterator.s(); !(_step = _iterator.n()).done;) {
              var prop = _step.value;
              var idx = binsearchInsert(result, prop, function (a, b) {
                return a.compare(b);
              });

              // ordered insert
              result.splice(idx, 0, prop);
            }
          } catch (err) {
            _iterator.e(err);
          } finally {
            _iterator.f();
          }
        }
        return result;
      }

      /**
       * Initialize the recurrence expansion.
       *
       * @private
       * @param {ICAL.Component} component    The component to initialize from.
       */
    }, {
      key: "_init",
      value: function _init(component) {
        this.ruleIterators = [];
        this.last = this.dtstart.clone();

        // to provide api consistency non-recurring
        // events can also use the iterator though it will
        // only return a single time.
        if (!component.hasProperty('rdate') && !component.hasProperty('rrule') && !component.hasProperty('recurrence-id')) {
          this.ruleDate = this.last.clone();
          this.complete = true;
          return;
        }
        if (component.hasProperty('rdate')) {
          this.ruleDates = this._extractDates(component, 'rdate');

          // special hack for cases where first rdate is prior
          // to the start date. We only check for the first rdate.
          // This is mostly for google's crazy recurring date logic
          // (contacts birthdays).
          if (this.ruleDates[0] && this.ruleDates[0].compare(this.dtstart) < 0) {
            this.ruleDateInc = 0;
            this.last = this.ruleDates[0].clone();
          } else {
            this.ruleDateInc = binsearchInsert(this.ruleDates, this.last, function (a, b) {
              return a.compare(b);
            });
          }
          this.ruleDate = this.ruleDates[this.ruleDateInc];
        }
        if (component.hasProperty('rrule')) {
          var rules = component.getAllProperties('rrule');
          var i = 0;
          var len = rules.length;
          var rule;
          var iter;
          for (; i < len; i++) {
            rule = rules[i].getFirstValue();
            iter = rule.iterator(this.dtstart);
            this.ruleIterators.push(iter);

            // increment to the next occurrence so future
            // calls to next return times beyond the initial iteration.
            // XXX: I find this suspicious might be a bug?
            iter.next();
          }
        }
        if (component.hasProperty('exdate')) {
          this.exDates = this._extractDates(component, 'exdate');
          // if we have a .last day we increment the index to beyond it.
          this.exDateInc = binsearchInsert(this.exDates, this.last, function (a, b) {
            return a.compare(b);
          });
          this.exDate = this.exDates[this.exDateInc];
        }
      }

      /**
       * Advance to the next exdate
       * @private
       */
    }, {
      key: "_nextExDay",
      value: function _nextExDay() {
        this.exDate = this.exDates[++this.exDateInc];
      }

      /**
       * Advance to the next rule date
       * @private
       */
    }, {
      key: "_nextRuleDay",
      value: function _nextRuleDay() {
        this.ruleDate = this.ruleDates[++this.ruleDateInc];
      }

      /**
       * Find and return the recurrence rule with the most recent event and
       * return it.
       *
       * @private
       * @return {?ICAL.RecurIterator}    Found iterator.
       */
    }, {
      key: "_nextRecurrenceIter",
      value: function _nextRecurrenceIter() {
        var iters = this.ruleIterators;
        if (iters.length === 0) {
          return null;
        }
        var len = iters.length;
        var iter;
        var iterTime;
        var iterIdx = 0;
        var chosenIter;

        // loop through each iterator
        for (; iterIdx < len; iterIdx++) {
          iter = iters[iterIdx];
          iterTime = iter.last;

          // if iteration is complete
          // then we must exclude it from
          // the search and remove it.
          if (iter.completed) {
            len--;
            if (iterIdx !== 0) {
              iterIdx--;
            }
            iters.splice(iterIdx, 1);
            continue;
          }

          // find the most recent possible choice
          if (!chosenIter || chosenIter.last.compare(iterTime) > 0) {
            // that iterator is saved
            chosenIter = iter;
          }
        }

        // the chosen iterator is returned but not mutated
        // this iterator contains the most recent event.
        return chosenIter;
      }
    }]);
  }();

  /**
   * ICAL.js is organized into multiple layers. The bottom layer is a raw jCal
   * object, followed by the component/property layer. The highest level is the
   * event representation, which this class is part of. See the
   * {@tutorial layers} guide for more details.
   *
   * @class
   * @alias ICAL.Event
   */
  var Event = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.Event instance.
     *
     * @param {ICAL.Component=} component         The ICAL.Component to base this event on
     * @param {Object} options                    Options for this event
     * @param {Boolean} options.strictExceptions  When true, will verify exceptions are related by
     *                                              their UUID
     * @param {Array<ICAL.Component|ICAL.Event>} options.exceptions
     *          Exceptions to this event, either as components or events. If not
     *            specified exceptions will automatically be set in relation of
     *            component's parent
     */
    function Event(component, options) {
      _classCallCheck(this, Event);
      /**
       * List of related event exceptions.
       *
       * @type {ICAL.Event[]}
       */
      _defineProperty(this, "exceptions", null);
      /**
       * When true, will verify exceptions are related by their UUID.
       *
       * @type {Boolean}
       */
      _defineProperty(this, "strictExceptions", false);
      if (!(component instanceof Component)) {
        options = component;
        component = null;
      }
      if (component) {
        this.component = component;
      } else {
        this.component = new Component('vevent');
      }
      this._rangeExceptionCache = Object.create(null);
      this.exceptions = Object.create(null);
      this.rangeExceptions = [];
      if (options && options.strictExceptions) {
        this.strictExceptions = options.strictExceptions;
      }
      if (options && options.exceptions) {
        options.exceptions.forEach(this.relateException, this);
      } else if (this.component.parent && !this.isRecurrenceException()) {
        this.component.parent.getAllSubcomponents('vevent').forEach(function (event) {
          if (event.hasProperty('recurrence-id')) {
            this.relateException(event);
          }
        }, this);
      }
    }
    return _createClass(Event, [{
      key: "relateException",
      value:
      /**
       * Relates a given event exception to this object.  If the given component
       * does not share the UID of this event it cannot be related and will throw
       * an exception.
       *
       * If this component is an exception it cannot have other exceptions
       * related to it.
       *
       * @param {ICAL.Component|ICAL.Event} obj       Component or event
       */
      function relateException(obj) {
        if (this.isRecurrenceException()) {
          throw new Error('cannot relate exception to exceptions');
        }
        if (obj instanceof Component) {
          obj = new Event(obj);
        }
        if (this.strictExceptions && obj.uid !== this.uid) {
          throw new Error('attempted to relate unrelated exception');
        }
        var id = obj.recurrenceId.toString();

        // we don't sort or manage exceptions directly
        // here the recurrence expander handles that.
        this.exceptions[id] = obj;

        // index RANGE=THISANDFUTURE exceptions so we can
        // look them up later in getOccurrenceDetails.
        if (obj.modifiesFuture()) {
          var item = [obj.recurrenceId.toUnixTime(), id];

          // we keep them sorted so we can find the nearest
          // value later on...
          var idx = binsearchInsert(this.rangeExceptions, item, compareRangeException);
          this.rangeExceptions.splice(idx, 0, item);
        }
      }

      /**
       * Checks if this record is an exception and has the RANGE=THISANDFUTURE
       * value.
       *
       * @return {Boolean}        True, when exception is within range
       */
    }, {
      key: "modifiesFuture",
      value: function modifiesFuture() {
        if (!this.component.hasProperty('recurrence-id')) {
          return false;
        }
        var range = this.component.getFirstProperty('recurrence-id').getParameter('range');
        return range === Event.THISANDFUTURE;
      }

      /**
       * Finds the range exception nearest to the given date.
       *
       * @param {ICAL.Time} time usually an occurrence time of an event
       * @return {?ICAL.Event} the related event/exception or null
       */
    }, {
      key: "findRangeException",
      value: function findRangeException(time) {
        if (!this.rangeExceptions.length) {
          return null;
        }
        var utc = time.toUnixTime();
        var idx = binsearchInsert(this.rangeExceptions, [utc], compareRangeException);
        idx -= 1;

        // occurs before
        if (idx < 0) {
          return null;
        }
        var rangeItem = this.rangeExceptions[idx];

        /* c8 ignore next 4 */
        if (utc < rangeItem[0]) {
          // sanity check only
          return null;
        }
        return rangeItem[1];
      }

      /**
       * This object is returned by {@link ICAL.Event#getOccurrenceDetails getOccurrenceDetails}
       *
       * @typedef {Object} occurrenceDetails
       * @memberof ICAL.Event
       * @property {ICAL.Time} recurrenceId       The passed in recurrence id
       * @property {ICAL.Event} item              The occurrence
       * @property {ICAL.Time} startDate          The start of the occurrence
       * @property {ICAL.Time} endDate            The end of the occurrence
       */

      /**
       * Returns the occurrence details based on its start time.  If the
       * occurrence has an exception will return the details for that exception.
       *
       * NOTE: this method is intend to be used in conjunction
       *       with the {@link ICAL.Event#iterator iterator} method.
       *
       * @param {ICAL.Time} occurrence time occurrence
       * @return {ICAL.Event.occurrenceDetails} Information about the occurrence
       */
    }, {
      key: "getOccurrenceDetails",
      value: function getOccurrenceDetails(occurrence) {
        var id = occurrence.toString();
        var utcId = occurrence.convertToZone(Timezone.utcTimezone).toString();
        var item;
        var result = {
          //XXX: Clone?
          recurrenceId: occurrence
        };
        if (id in this.exceptions) {
          item = result.item = this.exceptions[id];
          result.startDate = item.startDate;
          result.endDate = item.endDate;
          result.item = item;
        } else if (utcId in this.exceptions) {
          item = this.exceptions[utcId];
          result.startDate = item.startDate;
          result.endDate = item.endDate;
          result.item = item;
        } else {
          // range exceptions (RANGE=THISANDFUTURE) have a
          // lower priority then direct exceptions but
          // must be accounted for first. Their item is
          // always the first exception with the range prop.
          var rangeExceptionId = this.findRangeException(occurrence);
          var end;
          if (rangeExceptionId) {
            var exception = this.exceptions[rangeExceptionId];

            // range exception must modify standard time
            // by the difference (if any) in start/end times.
            result.item = exception;
            var startDiff = this._rangeExceptionCache[rangeExceptionId];
            if (!startDiff) {
              var original = exception.recurrenceId.clone();
              var newStart = exception.startDate.clone();

              // zones must be same otherwise subtract may be incorrect.
              original.zone = newStart.zone;
              startDiff = newStart.subtractDate(original);
              this._rangeExceptionCache[rangeExceptionId] = startDiff;
            }
            var start = occurrence.clone();
            start.zone = exception.startDate.zone;
            start.addDuration(startDiff);
            end = start.clone();
            end.addDuration(exception.duration);
            result.startDate = start;
            result.endDate = end;
          } else {
            // no range exception standard expansion
            end = occurrence.clone();
            end.addDuration(this.duration);
            result.endDate = end;
            result.startDate = occurrence;
            result.item = this;
          }
        }
        return result;
      }

      /**
       * Builds a recur expansion instance for a specific point in time (defaults
       * to startDate).
       *
       * @param {ICAL.Time} startTime     Starting point for expansion
       * @return {ICAL.RecurExpansion}    Expansion object
       */
    }, {
      key: "iterator",
      value: function iterator(startTime) {
        return new RecurExpansion({
          component: this.component,
          dtstart: startTime || this.startDate
        });
      }

      /**
       * Checks if the event is recurring
       *
       * @return {Boolean}        True, if event is recurring
       */
    }, {
      key: "isRecurring",
      value: function isRecurring() {
        var comp = this.component;
        return comp.hasProperty('rrule') || comp.hasProperty('rdate');
      }

      /**
       * Checks if the event describes a recurrence exception. See
       * {@tutorial terminology} for details.
       *
       * @return {Boolean}    True, if the event describes a recurrence exception
       */
    }, {
      key: "isRecurrenceException",
      value: function isRecurrenceException() {
        return this.component.hasProperty('recurrence-id');
      }

      /**
       * Returns the types of recurrences this event may have.
       *
       * Returned as an object with the following possible keys:
       *
       *    - YEARLY
       *    - MONTHLY
       *    - WEEKLY
       *    - DAILY
       *    - MINUTELY
       *    - SECONDLY
       *
       * @return {Object.<ICAL.Recur.frequencyValues, Boolean>}
       *          Object of recurrence flags
       */
    }, {
      key: "getRecurrenceTypes",
      value: function getRecurrenceTypes() {
        var rules = this.component.getAllProperties('rrule');
        var i = 0;
        var len = rules.length;
        var result = Object.create(null);
        for (; i < len; i++) {
          var value = rules[i].getFirstValue();
          result[value.freq] = true;
        }
        return result;
      }

      /**
       * The uid of this event
       * @type {String}
       */
    }, {
      key: "uid",
      get: function get() {
        return this._firstProp('uid');
      },
      set: function set(value) {
        this._setProp('uid', value);
      }

      /**
       * The start date
       * @type {ICAL.Time}
       */
    }, {
      key: "startDate",
      get: function get() {
        return this._firstProp('dtstart');
      },
      set: function set(value) {
        this._setTime('dtstart', value);
      }

      /**
       * The end date. This can be the result directly from the property, or the
       * end date calculated from start date and duration. Setting the property
       * will remove any duration properties.
       * @type {ICAL.Time}
       */
    }, {
      key: "endDate",
      get: function get() {
        var endDate = this._firstProp('dtend');
        if (!endDate) {
          var duration = this._firstProp('duration');
          endDate = this.startDate.clone();
          if (duration) {
            endDate.addDuration(duration);
          } else if (endDate.isDate) {
            endDate.day += 1;
          }
        }
        return endDate;
      },
      set: function set(value) {
        if (this.component.hasProperty('duration')) {
          this.component.removeProperty('duration');
        }
        this._setTime('dtend', value);
      }

      /**
       * The duration. This can be the result directly from the property, or the
       * duration calculated from start date and end date. Setting the property
       * will remove any `dtend` properties.
       * @type {ICAL.Duration}
       */
    }, {
      key: "duration",
      get: function get() {
        var duration = this._firstProp('duration');
        if (!duration) {
          return this.endDate.subtractDateTz(this.startDate);
        }
        return duration;
      },
      set: function set(value) {
        if (this.component.hasProperty('dtend')) {
          this.component.removeProperty('dtend');
        }
        this._setProp('duration', value);
      }

      /**
       * The location of the event.
       * @type {String}
       */
    }, {
      key: "location",
      get: function get() {
        return this._firstProp('location');
      },
      set: function set(value) {
        this._setProp('location', value);
      }

      /**
       * The attendees in the event
       * @type {ICAL.Property[]}
       * @readonly
       */
    }, {
      key: "attendees",
      get: function get() {
        //XXX: This is way lame we should have a better
        //     data structure for this later.
        return this.component.getAllProperties('attendee');
      }

      /**
       * The event summary
       * @type {String}
       */
    }, {
      key: "summary",
      get: function get() {
        return this._firstProp('summary');
      },
      set: function set(value) {
        this._setProp('summary', value);
      }

      /**
       * The event description.
       * @type {String}
       */
    }, {
      key: "description",
      get: function get() {
        return this._firstProp('description');
      },
      set: function set(value) {
        this._setProp('description', value);
      }

      /**
       * The event color from [rfc7986](https://datatracker.ietf.org/doc/html/rfc7986)
       * @type {String}
       */
    }, {
      key: "color",
      get: function get() {
        return this._firstProp('color');
      },
      set: function set(value) {
        this._setProp('color', value);
      }

      /**
       * The organizer value as an uri. In most cases this is a mailto: uri, but
       * it can also be something else, like urn:uuid:...
       * @type {String}
       */
    }, {
      key: "organizer",
      get: function get() {
        return this._firstProp('organizer');
      },
      set: function set(value) {
        this._setProp('organizer', value);
      }

      /**
       * The sequence value for this event. Used for scheduling
       * see {@tutorial terminology}.
       * @type {Number}
       */
    }, {
      key: "sequence",
      get: function get() {
        return this._firstProp('sequence');
      },
      set: function set(value) {
        this._setProp('sequence', value);
      }

      /**
       * The recurrence id for this event. See {@tutorial terminology} for details.
       * @type {ICAL.Time}
       */
    }, {
      key: "recurrenceId",
      get: function get() {
        return this._firstProp('recurrence-id');
      },
      set: function set(value) {
        this._setTime('recurrence-id', value);
      }

      /**
       * Set/update a time property's value.
       * This will also update the TZID of the property.
       *
       * TODO: this method handles the case where we are switching
       * from a known timezone to an implied timezone (one without TZID).
       * This does _not_ handle the case of moving between a known
       *  (by TimezoneService) timezone to an unknown timezone...
       *
       * We will not add/remove/update the VTIMEZONE subcomponents
       *  leading to invalid ICAL data...
       * @private
       * @param {String} propName     The property name
       * @param {ICAL.Time} time      The time to set
       */
    }, {
      key: "_setTime",
      value: function _setTime(propName, time) {
        var prop = this.component.getFirstProperty(propName);
        if (!prop) {
          prop = new Property(propName);
          this.component.addProperty(prop);
        }

        // utc and local don't get a tzid
        if (time.zone === Timezone.localTimezone || time.zone === Timezone.utcTimezone) {
          // remove the tzid
          prop.removeParameter('tzid');
        } else {
          prop.setParameter('tzid', time.zone.tzid);
        }
        prop.setValue(time);
      }
    }, {
      key: "_setProp",
      value: function _setProp(name, value) {
        this.component.updatePropertyWithValue(name, value);
      }
    }, {
      key: "_firstProp",
      value: function _firstProp(name) {
        return this.component.getFirstPropertyValue(name);
      }

      /**
       * The string representation of this event.
       * @return {String}
       */
    }, {
      key: "toString",
      value: function toString() {
        return this.component.toString();
      }
    }]);
  }();
  _defineProperty(Event, "THISANDFUTURE", 'THISANDFUTURE');
  function compareRangeException(a, b) {
    if (a[0] > b[0]) return 1;
    if (b[0] > a[0]) return -1;
    return 0;
  }

  /**
   * The ComponentParser is used to process a String or jCal Object,
   * firing callbacks for various found components, as well as completion.
   *
   * @example
   * var options = {
   *   // when false no events will be emitted for type
   *   parseEvent: true,
   *   parseTimezone: true
   * };
   *
   * var parser = new ICAL.ComponentParser(options);
   *
   * parser.onevent(eventComponent) {
   *   //...
   * }
   *
   * // ontimezone, etc...
   *
   * parser.oncomplete = function() {
   *
   * };
   *
   * parser.process(stringOrComponent);
   *
   * @class
   * @alias ICAL.ComponentParser
   */
  var ComponentParser = /*#__PURE__*/function () {
    /**
     * Creates a new ICAL.ComponentParser instance.
     *
     * @param {Object=} options                   Component parser options
     * @param {Boolean} options.parseEvent        Whether events should be parsed
     * @param {Boolean} options.parseTimezeone    Whether timezones should be parsed
     */
    function ComponentParser(options) {
      _classCallCheck(this, ComponentParser);
      /**
       * When true, parse events
       *
       * @type {Boolean}
       */
      _defineProperty(this, "parseEvent", true);
      /**
       * When true, parse timezones
       *
       * @type {Boolean}
       */
      _defineProperty(this, "parseTimezone", true);
      /* SAX like events here for reference */
      /**
       * Fired when parsing is complete
       * @callback
       */
      _defineProperty(this, "oncomplete", /* c8 ignore next */function () {});
      /**
       * Fired if an error occurs during parsing.
       *
       * @callback
       * @param {Error} err details of error
       */
      _defineProperty(this, "onerror", /* c8 ignore next */function (err) {});
      /**
       * Fired when a top level component (VTIMEZONE) is found
       *
       * @callback
       * @param {ICAL.Timezone} component     Timezone object
       */
      _defineProperty(this, "ontimezone", /* c8 ignore next */function (component) {});
      /**
       * Fired when a top level component (VEVENT) is found.
       *
       * @callback
       * @param {ICAL.Event} component    Top level component
       */
      _defineProperty(this, "onevent", /* c8 ignore next */function (component) {});
      if (typeof options === 'undefined') {
        options = {};
      }
      for (var _i = 0, _Object$entries = Object.entries(options); _i < _Object$entries.length; _i++) {
        var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
          key = _Object$entries$_i[0],
          value = _Object$entries$_i[1];
        this[key] = value;
      }
    }
    return _createClass(ComponentParser, [{
      key: "process",
      value:
      /**
       * Process a string or parse ical object.  This function itself will return
       * nothing but will start the parsing process.
       *
       * Events must be registered prior to calling this method.
       *
       * @param {ICAL.Component|String|Object} ical      The component to process,
       *        either in its final form, as a jCal Object, or string representation
       */
      function process(ical) {
        //TODO: this is sync now in the future we will have a incremental parser.
        if (typeof ical === 'string') {
          ical = parse(ical);
        }
        if (!(ical instanceof Component)) {
          ical = new Component(ical);
        }
        var components = ical.getAllSubcomponents();
        var i = 0;
        var len = components.length;
        var component;
        for (; i < len; i++) {
          component = components[i];
          switch (component.name) {
            case 'vtimezone':
              if (this.parseTimezone) {
                var tzid = component.getFirstPropertyValue('tzid');
                if (tzid) {
                  this.ontimezone(new Timezone({
                    tzid: tzid,
                    component: component
                  }));
                }
              }
              break;
            case 'vevent':
              if (this.parseEvent) {
                this.onevent(new Event(component));
              }
              break;
            default:
              continue;
          }
        }

        //XXX: ideally we should do a "nextTick" here
        //     so in all cases this is actually async.
        this.oncomplete();
      }
    }]);
  }();

  /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   * Portions Copyright (C) Philipp Kewisch */

  /**
   * The main ICAL module. Provides access to everything else.
   *
   * @alias ICAL
   * @namespace ICAL
   * @property {ICAL.design} design
   * @property {ICAL.helpers} helpers
   */
  var ICALmodule = {
    /**
     * The number of characters before iCalendar line folding should occur
     * @type {Number}
     * @default 75
     */
    foldLength: 75,
    debug: false,
    /**
     * The character(s) to be used for a newline. The default value is provided by
     * rfc5545.
     * @type {String}
     * @default "\r\n"
     */
    newLineChar: '\r\n',
    Binary: Binary,
    Component: Component,
    ComponentParser: ComponentParser,
    Duration: Duration,
    Event: Event,
    Period: Period,
    Property: Property,
    Recur: Recur,
    RecurExpansion: RecurExpansion,
    RecurIterator: RecurIterator,
    Time: Time,
    Timezone: Timezone,
    TimezoneService: TimezoneService,
    UtcOffset: UtcOffset,
    VCardTime: VCardTime,
    parse: parse,
    stringify: stringify,
    design: design,
    helpers: helpers
  };

  return ICALmodule;

}));
